Skip to content

Commit c9249e2

Browse files
authored
Support every argument syntax for clone() (#18938)
* zend_language_parser: Support every argument syntax for `clone()` * zend_language_parser: Adjust `clone()` grammar to avoid conflicts * zend_language_parser: Add explanatory comment for `clone_argument_list`
1 parent bbc465e commit c9249e2

File tree

2 files changed

+99
-7
lines changed

2 files changed

+99
-7
lines changed

‎Zend/tests/clone/ast.phpt

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,60 @@ try {
1818
echo $e->getMessage(), PHP_EOL;
1919
}
2020

21+
try {
22+
assert(false && $y = clone($x, ));
23+
} catch (Error $e) {
24+
echo $e->getMessage(), PHP_EOL;
25+
}
26+
27+
try {
28+
assert(false && $y = clone($x, [ "foo" => $foo, "bar" => $bar ]));
29+
} catch (Error $e) {
30+
echo $e->getMessage(), PHP_EOL;
31+
}
32+
33+
try {
34+
assert(false && $y = clone($x, $array));
35+
} catch (Error $e) {
36+
echo $e->getMessage(), PHP_EOL;
37+
}
38+
39+
try {
40+
assert(false && $y = clone($x, $array, $extraParameter, $trailingComma, ));
41+
} catch (Error $e) {
42+
echo $e->getMessage(), PHP_EOL;
43+
}
44+
45+
try {
46+
assert(false && $y = clone(object: $x, withProperties: [ "foo" => $foo, "bar" => $bar ]));
47+
} catch (Error $e) {
48+
echo $e->getMessage(), PHP_EOL;
49+
}
50+
51+
try {
52+
assert(false && $y = clone($x, withProperties: [ "foo" => $foo, "bar" => $bar ]));
53+
} catch (Error $e) {
54+
echo $e->getMessage(), PHP_EOL;
55+
}
56+
57+
try {
58+
assert(false && $y = clone(object: $x));
59+
} catch (Error $e) {
60+
echo $e->getMessage(), PHP_EOL;
61+
}
62+
63+
try {
64+
assert(false && $y = clone(object: $x, [ "foo" => $foo, "bar" => $bar ]));
65+
} catch (Error $e) {
66+
echo $e->getMessage(), PHP_EOL;
67+
}
68+
69+
try {
70+
assert(false && $y = clone(...["object" => $x, "withProperties" => [ "foo" => $foo, "bar" => $bar ]]));
71+
} catch (Error $e) {
72+
echo $e->getMessage(), PHP_EOL;
73+
}
74+
2175
try {
2276
assert(false && $y = clone(...));
2377
} catch (Error $e) {
@@ -28,4 +82,13 @@ try {
2882
--EXPECT--
2983
assert(false && ($y = \clone($x)))
3084
assert(false && ($y = \clone($x)))
85+
assert(false && ($y = \clone($x)))
86+
assert(false && ($y = \clone($x, ['foo' => $foo, 'bar' => $bar])))
87+
assert(false && ($y = \clone($x, $array)))
88+
assert(false && ($y = \clone($x, $array, $extraParameter, $trailingComma)))
89+
assert(false && ($y = \clone(object: $x, withProperties: ['foo' => $foo, 'bar' => $bar])))
90+
assert(false && ($y = \clone($x, withProperties: ['foo' => $foo, 'bar' => $bar])))
91+
assert(false && ($y = \clone(object: $x)))
92+
assert(false && ($y = \clone(object: $x, ['foo' => $foo, 'bar' => $bar])))
93+
assert(false && ($y = \clone(...['object' => $x, 'withProperties' => ['foo' => $foo, 'bar' => $bar]])))
3194
assert(false && ($y = \clone(...)))

‎Zend/zend_language_parser.y

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
259259
%type <ast> unprefixed_use_declarations const_decl inner_statement
260260
%type <ast> expr optional_expr while_statement for_statement foreach_variable
261261
%type <ast> foreach_statement declare_statement finally_statement unset_variable variable
262-
%type <ast> extends_from parameter optional_type_without_static argument global_var
262+
%type <ast> extends_from parameter optional_type_without_static argument argument_no_expr global_var
263263
%type <ast> static_var class_statement trait_adaptation trait_precedence trait_alias
264264
%type <ast> absolute_trait_method_reference trait_method_reference property echo_expr
265265
%type <ast> new_dereferenceable new_non_dereferenceable anonymous_class class_name class_name_reference simple_variable
@@ -287,7 +287,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
287287
%type <ast> enum_declaration_statement enum_backing_type enum_case enum_case_expr
288288
%type <ast> function_name non_empty_member_modifiers
289289
%type <ast> property_hook property_hook_list optional_property_hook_list hooked_property property_hook_body
290-
%type <ast> optional_parameter_list
290+
%type <ast> optional_parameter_list clone_argument_list non_empty_clone_argument_list
291291

292292
%type <num> returns_ref function fn is_reference is_variadic property_modifiers property_hook_modifiers
293293
%type <num> method_modifiers class_const_modifiers member_modifier optional_cpp_modifiers
@@ -914,13 +914,42 @@ non_empty_argument_list:
914914
{ $$ = zend_ast_list_add($1, $3); }
915915
;
916916

917-
argument:
918-
expr { $$ = $1; }
919-
| identifier ':' expr
917+
/* `clone_argument_list` is necessary to resolve a parser ambiguity (shift-reduce conflict)
918+
* of `clone($expr)`, which could either be parsed as a function call with `$expr` as the first
919+
* argument or as a use of the `clone` language construct with an expression with useless
920+
* parenthesis. Both would be valid and result in the same AST / the same semantics.
921+
* `clone_argument_list` is defined in a way that an `expr` in the first position needs to
922+
* be followed by a `,` which is not valid syntax for a parenthesized `expr`, ensuring
923+
* that calling `clone()` with a single unnamed parameter is handled by the language construct
924+
* syntax.
925+
*/
926+
clone_argument_list:
927+
'(' ')' { $$ = zend_ast_create_list(0, ZEND_AST_ARG_LIST); }
928+
| '(' non_empty_clone_argument_list possible_comma ')' { $$ = $2; }
929+
| '(' expr ',' ')' { $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, $2); }
930+
| '(' T_ELLIPSIS ')' { $$ = zend_ast_create_fcc(); }
931+
;
932+
933+
non_empty_clone_argument_list:
934+
expr ',' argument
935+
{ $$ = zend_ast_create_list(2, ZEND_AST_ARG_LIST, $1, $3); }
936+
| argument_no_expr
937+
{ $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, $1); }
938+
| non_empty_clone_argument_list ',' argument
939+
{ $$ = zend_ast_list_add($1, $3); }
940+
;
941+
942+
argument_no_expr:
943+
identifier ':' expr
920944
{ $$ = zend_ast_create(ZEND_AST_NAMED_ARG, $1, $3); }
921945
| T_ELLIPSIS expr { $$ = zend_ast_create(ZEND_AST_UNPACK, $2); }
922946
;
923947

948+
argument:
949+
expr { $$ = $1; }
950+
| argument_no_expr { $$ = $1; }
951+
;
952+
924953
global_var_list:
925954
global_var_list ',' global_var { $$ = zend_ast_list_add($1, $3); }
926955
| global_var { $$ = zend_ast_create_list(1, ZEND_AST_STMT_LIST, $1); }
@@ -1228,10 +1257,10 @@ expr:
12281257
{ $$ = zend_ast_create(ZEND_AST_ASSIGN, $1, $3); }
12291258
| variable '=' ampersand variable
12301259
{ $$ = zend_ast_create(ZEND_AST_ASSIGN_REF, $1, $4); }
1231-
| T_CLONE '(' T_ELLIPSIS ')' {
1260+
| T_CLONE clone_argument_list {
12321261
zend_ast *name = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_CLONE));
12331262
name->attr = ZEND_NAME_FQ;
1234-
$$ = zend_ast_create(ZEND_AST_CALL, name, zend_ast_create_fcc());
1263+
$$ = zend_ast_create(ZEND_AST_CALL, name, $2);
12351264
}
12361265
| T_CLONE expr {
12371266
zend_ast *name = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_CLONE));

0 commit comments

Comments
 (0)