Skip to content

Commit 07946d3

Browse files
committed
Add support for nullsafe property fetch in constant expressions
1 parent e1555ed commit 07946d3

File tree

10 files changed

+142
-7
lines changed

10 files changed

+142
-7
lines changed

Zend/tests/prop_const_expr/__get.phpt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ class A {
1010
}
1111

1212
const A_prop = (new A)->prop;
13-
1413
var_dump(A_prop);
1514

15+
const A_prop_nullsafe = (new A)?->prop;
16+
var_dump(A_prop_nullsafe);
17+
1618
?>
1719
--EXPECT--
1820
string(4) "prop"
21+
string(4) "prop"

Zend/tests/prop_const_expr/basic.phpt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,19 @@ var_dump(A_prop);
1919
var_dump(B_name);
2020
var_dump(B_value);
2121

22+
const A_prop_nullsafe = (new A)?->prop;
23+
const B_name_nullsafe = B::Case?->name;
24+
const B_value_nullsafe = B::Case?->value;
25+
26+
var_dump(A_prop_nullsafe);
27+
var_dump(B_name_nullsafe);
28+
var_dump(B_value_nullsafe);
29+
2230
?>
2331
--EXPECT--
2432
string(8) "A::$prop"
2533
string(4) "Case"
2634
string(7) "B::Case"
35+
string(8) "A::$prop"
36+
string(4) "Case"
37+
string(7) "B::Case"
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
--TEST--
2+
Nullsafe property constant expression
3+
--FILE--
4+
<?php
5+
6+
class Printer {
7+
public $test = 42;
8+
9+
public function __construct() {
10+
echo "Printer\n";
11+
}
12+
}
13+
14+
const A = (null)?->test;
15+
var_dump(A);
16+
17+
const B = (null)?->test->test;
18+
var_dump(B);
19+
20+
const C = (null)->test?->test;
21+
var_dump(C);
22+
23+
const D = (null)?->test['test'];
24+
var_dump(D);
25+
26+
const E = (null)['test']?->test;
27+
var_dump(E);
28+
29+
const F = (null)?->{new Printer};
30+
var_dump(F);
31+
32+
const G = (null)?->test + (new Printer)->test;
33+
var_dump(G);
34+
35+
?>
36+
--EXPECTF--
37+
NULL
38+
NULL
39+
40+
Warning: Attempt to read property "test" on null in %s on line %d
41+
NULL
42+
NULL
43+
44+
Warning: Trying to access array offset on value of type null in %s on line %d
45+
NULL
46+
NULL
47+
Printer
48+
int(42)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
Nullsafe property constant expression lhs error
3+
--FILE--
4+
<?php
5+
6+
const A_prop = (new A)?->prop;
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: Uncaught Error: Class "A" not found in %s:%d
11+
Stack trace:
12+
#0 {main}
13+
thrown in %s on line %d

Zend/tests/prop_const_expr/lhs_non_object.phpt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ Property constant expression lhs wrong type
44
<?php
55

66
const A_prop = (42)->prop;
7-
87
var_dump(A_prop);
98

9+
const A_prop_nullsafe = (42)?->prop;
10+
var_dump(A_prop_nullsafe);
11+
1012
?>
1113
--EXPECTF--
1214
Warning: Attempt to read property "prop" on int in %s on line %d
1315
NULL
16+
17+
Warning: Attempt to read property "prop" on int in %s on line %d
18+
NULL

Zend/tests/prop_const_expr/refcount.phpt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ class B {
1616
const A = (new B)->a;
1717
debug_zval_dump(A);
1818

19+
const A2 = (new B)?->a;
20+
debug_zval_dump(A2);
21+
1922
?>
2023
--EXPECT--
2124
object(A)#2 (0) refcount(2){
2225
}
26+
object(A)#3 (0) refcount(2){
27+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Nullsafe property constant expression rhs wrong type
3+
--FILE--
4+
<?php
5+
6+
class A {}
7+
class B {}
8+
9+
const A_prop = (new A)?->{new B};
10+
11+
var_dump(A_prop);
12+
13+
?>
14+
--EXPECTF--
15+
Fatal error: Uncaught Error: Object of class B could not be converted to string in %s:%d
16+
Stack trace:
17+
#0 {main}
18+
thrown in %s on line %d

Zend/tests/prop_const_expr/rhs_prop_not_found.phpt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@ Property constant expression rhs error
66
class A {}
77

88
const A_prop = (new A)->prop;
9-
109
var_dump(A_prop);
1110

11+
const A_prop_nullsafe = (new A)?->prop;
12+
var_dump(A_prop_nullsafe);
13+
1214
?>
1315
--EXPECTF--
1416
Warning: Undefined property: A::$prop in %s on line %d
1517
NULL
18+
19+
Warning: Undefined property: A::$prop in %s on line %d
20+
NULL

Zend/zend_ast.c

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -498,10 +498,11 @@ zend_class_entry *zend_ast_fetch_class(zend_ast *ast, zend_class_entry *scope)
498498
return zend_fetch_class_with_scope(zend_ast_get_str(ast), ast->attr | ZEND_FETCH_CLASS_EXCEPTION, scope);
499499
}
500500

501-
ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_class_entry *scope)
501+
ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_ex(zval *result, zend_ast *ast, zend_class_entry *scope, bool *short_circuited_ptr)
502502
{
503503
zval op1, op2;
504504
zend_result ret = SUCCESS;
505+
*short_circuited_ptr = false;
505506

506507
switch (ast->kind) {
507508
case ZEND_AST_BINARY_OP:
@@ -733,10 +734,16 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast
733734
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use [] for reading");
734735
}
735736

736-
if (UNEXPECTED(zend_ast_evaluate(&op1, ast->child[0], scope) != SUCCESS)) {
737+
bool short_circuited;
738+
if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited) != SUCCESS)) {
737739
ret = FAILURE;
738740
break;
739741
}
742+
if (short_circuited) {
743+
*short_circuited_ptr = true;
744+
ZVAL_NULL(result);
745+
return SUCCESS;
746+
}
740747

741748
// DIM on objects is disallowed because it allows executing arbitrary expressions
742749
if (Z_TYPE(op1) == IS_OBJECT) {
@@ -902,10 +909,23 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast
902909
return SUCCESS;
903910
}
904911
case ZEND_AST_PROP:
912+
case ZEND_AST_NULLSAFE_PROP:
905913
{
906-
if (UNEXPECTED(zend_ast_evaluate(&op1, ast->child[0], scope) != SUCCESS)) {
914+
bool short_circuited;
915+
if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited) != SUCCESS)) {
907916
return FAILURE;
908917
}
918+
if (short_circuited) {
919+
*short_circuited_ptr = true;
920+
ZVAL_NULL(result);
921+
return SUCCESS;
922+
}
923+
if (ast->kind == ZEND_AST_NULLSAFE_PROP && Z_TYPE(op1) == IS_NULL) {
924+
*short_circuited_ptr = true;
925+
ZVAL_NULL(result);
926+
return SUCCESS;
927+
}
928+
909929
if (UNEXPECTED(zend_ast_evaluate(&op2, ast->child[1], scope) != SUCCESS)) {
910930
zval_ptr_dtor_nogc(&op1);
911931
return FAILURE;
@@ -954,6 +974,12 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast
954974
return ret;
955975
}
956976

977+
ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_class_entry *scope)
978+
{
979+
bool short_circuited;
980+
return zend_ast_evaluate_ex(result, ast, scope, &short_circuited);
981+
}
982+
957983
static size_t ZEND_FASTCALL zend_ast_tree_size(zend_ast *ast)
958984
{
959985
size_t size;

Zend/zend_compile.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9666,7 +9666,8 @@ static bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */
96669666
|| kind == ZEND_AST_MAGIC_CONST || kind == ZEND_AST_COALESCE
96679667
|| kind == ZEND_AST_CONST_ENUM_INIT
96689668
|| kind == ZEND_AST_NEW || kind == ZEND_AST_ARG_LIST
9669-
|| kind == ZEND_AST_NAMED_ARG || kind == ZEND_AST_PROP;
9669+
|| kind == ZEND_AST_NAMED_ARG
9670+
|| kind == ZEND_AST_PROP || kind == ZEND_AST_NULLSAFE_PROP;
96709671
}
96719672
/* }}} */
96729673

0 commit comments

Comments
 (0)