Skip to content

Commit 8ceaa77

Browse files
committed
Add support for property initialization during cloning
1 parent 7edf034 commit 8ceaa77

18 files changed

+1621
-611
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Test that the property initializer list is required when "with" is given
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public function withProperties()
9+
{
10+
return clone $this with;
11+
}
12+
}
13+
14+
?>
15+
--EXPECTF--
16+
Parse error: syntax error, unexpected token ";", expecting "{" in %s on line %d
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Test that the property initializer list cannot contain integer keys
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public function withProperties()
9+
{
10+
return clone $this with {1: "value"};
11+
}
12+
}
13+
14+
?>
15+
--EXPECTF--
16+
Parse error: syntax error, unexpected integer "1" in %s on line %d
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Test that the property initializer list can only contain literals
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public function withProperties()
9+
{
10+
$property = "string";
11+
12+
return clone $this with {$property: "value"};
13+
}
14+
}
15+
16+
?>
17+
--EXPECTF--
18+
Parse error: syntax error, unexpected variable "$property" in %s on line %d
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
Test that declared properties can be initialized during cloning
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public int $property1;
9+
public string $property2;
10+
11+
public function withProperties()
12+
{
13+
return clone $this with {
14+
property1: 1,
15+
property2: "foo",
16+
};
17+
}
18+
}
19+
20+
$foo = new Foo();
21+
var_dump($foo);
22+
$bar = $foo->withProperties();
23+
var_dump($bar);
24+
25+
?>
26+
--EXPECT--
27+
object(Foo)#1 (0) {
28+
["property1"]=>
29+
uninitialized(int)
30+
["property2"]=>
31+
uninitialized(string)
32+
}
33+
object(Foo)#2 (2) {
34+
["property1"]=>
35+
int(1)
36+
["property2"]=>
37+
string(3) "foo"
38+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
Test that dynamic properties can be initialized during cloning
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public function withProperties()
9+
{
10+
return clone $this with {
11+
property1: 1,
12+
property2: "foo",
13+
};
14+
}
15+
}
16+
17+
$foo = new Foo();
18+
var_dump($foo);
19+
$bar = $foo->withProperties();
20+
var_dump($bar);
21+
22+
?>
23+
--EXPECT--
24+
object(Foo)#1 (0) {
25+
}
26+
object(Foo)#2 (2) {
27+
["property1"]=>
28+
int(1)
29+
["property2"]=>
30+
string(3) "foo"
31+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Test that the property initializer list can be empty
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public function withProperties()
9+
{
10+
return clone $this with {};
11+
}
12+
}
13+
14+
$foo = new Foo();
15+
var_dump($foo);
16+
$bar = $foo->withProperties();
17+
var_dump($bar);
18+
19+
?>
20+
--EXPECT--
21+
object(Foo)#1 (0) {
22+
}
23+
object(Foo)#2 (0) {
24+
}

Zend/zend_ast.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1602,6 +1602,11 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
16021602
case ZEND_AST_MATCH_ARM_LIST:
16031603
zend_ast_export_list(str, (zend_ast_list*)ast, 0, 0, indent);
16041604
break;
1605+
case ZEND_AST_PROPERTY_INITIALIZER_LIST:
1606+
smart_str_appends(str, " {");
1607+
zend_ast_export_list(str, (zend_ast_list*)ast, 0, 0, indent);
1608+
smart_str_appends(str, " }");
1609+
break;
16051610
case ZEND_AST_CLOSURE_USES:
16061611
smart_str_appends(str, " use(");
16071612
zend_ast_export_var_list(str, (zend_ast_list*)ast, indent);
@@ -2079,6 +2084,12 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
20792084
ast = ast->child[1];
20802085
goto tail_call;
20812086

2087+
case ZEND_AST_INITIALIZER_EXPR:
2088+
smart_str_append(str, zend_ast_get_str(ast->child[0]));
2089+
smart_str_appends(str, "= ");
2090+
ast = ast->child[1];
2091+
goto tail_call;
2092+
20822093
/* 3 child nodes */
20832094
case ZEND_AST_METHOD_CALL:
20842095
case ZEND_AST_NULLSAFE_METHOD_CALL:

Zend/zend_ast.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ enum _zend_ast_kind {
6565
ZEND_AST_ATTRIBUTE_LIST,
6666
ZEND_AST_ATTRIBUTE_GROUP,
6767
ZEND_AST_MATCH_ARM_LIST,
68+
ZEND_AST_PROPERTY_INITIALIZER_LIST,
6869

6970
/* 0 child nodes */
7071
ZEND_AST_MAGIC_CONST = 0 << ZEND_AST_NUM_CHILDREN_SHIFT,
@@ -82,7 +83,6 @@ enum _zend_ast_kind {
8283
ZEND_AST_ISSET,
8384
ZEND_AST_SILENCE,
8485
ZEND_AST_SHELL_EXEC,
85-
ZEND_AST_CLONE,
8686
ZEND_AST_EXIT,
8787
ZEND_AST_PRINT,
8888
ZEND_AST_INCLUDE_OR_EVAL,
@@ -113,6 +113,7 @@ enum _zend_ast_kind {
113113
ZEND_AST_STATIC_PROP,
114114
ZEND_AST_CALL,
115115
ZEND_AST_CLASS_CONST,
116+
ZEND_AST_CLONE,
116117
ZEND_AST_ASSIGN,
117118
ZEND_AST_ASSIGN_REF,
118119
ZEND_AST_ASSIGN_OP,
@@ -147,6 +148,7 @@ enum _zend_ast_kind {
147148
ZEND_AST_MATCH,
148149
ZEND_AST_MATCH_ARM,
149150
ZEND_AST_NAMED_ARG,
151+
ZEND_AST_INITIALIZER_EXPR,
150152

151153
/* 3 child nodes */
152154
ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT,

Zend/zend_compile.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,7 @@ void zend_do_free(znode *op1) /* {{{ */
734734
case ZEND_ASSIGN_DIM_OP:
735735
case ZEND_ASSIGN_OBJ_OP:
736736
case ZEND_ASSIGN_STATIC_PROP_OP:
737+
case ZEND_INIT_OBJ:
737738
case ZEND_PRE_INC_STATIC_PROP:
738739
case ZEND_PRE_DEC_STATIC_PROP:
739740
case ZEND_PRE_INC_OBJ:
@@ -4538,11 +4539,40 @@ void zend_compile_new(znode *result, zend_ast *ast) /* {{{ */
45384539
void zend_compile_clone(znode *result, zend_ast *ast) /* {{{ */
45394540
{
45404541
zend_ast *obj_ast = ast->child[0];
4542+
zend_ast *initializer_ast = ast->child[1];
45414543

45424544
znode obj_node;
45434545
zend_compile_expr(&obj_node, obj_ast);
4546+
zend_op *opline = zend_emit_op(result, ZEND_CLONE, &obj_node, NULL);
45444547

4545-
zend_emit_op_tmp(result, ZEND_CLONE, &obj_node, NULL);
4548+
if (initializer_ast) {
4549+
zend_ast_list *list = zend_ast_get_list(initializer_ast);
4550+
uint32_t i;
4551+
4552+
for (i = 0; i < list->children; i++) {
4553+
zend_ast *property_init_expr_ast = list->child[i];
4554+
ZEND_ASSERT(property_init_expr_ast->kind == ZEND_AST_INITIALIZER_EXPR);
4555+
zend_string *property_name = zval_make_interned_string(zend_ast_get_zval(property_init_expr_ast->child[0]));
4556+
4557+
zend_ast *property_value_ast = property_init_expr_ast->child[1];
4558+
znode property_value_node;
4559+
zend_compile_expr(&property_value_node, property_value_ast);
4560+
4561+
znode property_object_node;
4562+
property_object_node.op_type = IS_VAR;
4563+
GET_NODE(&property_object_node, opline->result);
4564+
4565+
znode property_name_node;
4566+
property_name_node.op_type = IS_CONST;
4567+
ZVAL_STR_COPY(&property_name_node.u.constant, property_name);
4568+
opline = zend_emit_op(result, ZEND_INIT_OBJ, &property_object_node, &property_name_node);
4569+
opline->result.var = get_temporary_variable();
4570+
opline->extended_value = zend_alloc_cache_slots(3);
4571+
4572+
zend_emit_op_data(&property_value_node);
4573+
GET_NODE(result, opline->result);
4574+
}
4575+
}
45464576
}
45474577
/* }}} */
45484578

Zend/zend_language_parser.y

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
8080
%precedence '~' T_INT_CAST T_DOUBLE_CAST T_STRING_CAST T_ARRAY_CAST T_OBJECT_CAST T_BOOL_CAST T_UNSET_CAST '@'
8181
%right T_POW
8282
%precedence T_CLONE
83+
%precedence T_WITH
8384

8485
/* Resolve danging else conflict */
8586
%precedence T_NOELSE
@@ -113,6 +114,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
113114
%token <ident> T_INSTANCEOF "'instanceof'"
114115
%token <ident> T_NEW "'new'"
115116
%token <ident> T_CLONE "'clone'"
117+
%token <ident> T_WITH "'with'"
116118
%token <ident> T_EXIT "'exit'"
117119
%token <ident> T_IF "'if'"
118120
%token <ident> T_ELSEIF "'elseif'"
@@ -268,6 +270,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
268270
%type <ast> attributed_statement attributed_class_statement attributed_parameter
269271
%type <ast> attribute_decl attribute attributes attribute_group namespace_declaration_name
270272
%type <ast> match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list
273+
%type <ast> clone property_initializer_list non_empty_property_initializer_list property_initializer_expr
271274

272275
%type <num> returns_ref function fn is_reference is_variadic variable_modifiers
273276
%type <num> method_modifiers non_empty_member_modifiers member_modifier optional_visibility_modifier
@@ -286,7 +289,7 @@ start:
286289

287290
reserved_non_modifiers:
288291
T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
289-
| T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE | T_ENDWHILE
292+
| T_INSTANCEOF | T_NEW | T_CLONE | T_WITH | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE | T_ENDWHILE
290293
| T_FOR | T_ENDFOR | T_FOREACH | T_ENDFOREACH | T_DECLARE | T_ENDDECLARE | T_AS | T_TRY | T_CATCH | T_FINALLY
291294
| T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO
292295
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_BREAK
@@ -1024,7 +1027,7 @@ expr:
10241027
{ $$ = zend_ast_create(ZEND_AST_ASSIGN, $1, $3); }
10251028
| variable '=' '&' variable
10261029
{ $$ = zend_ast_create(ZEND_AST_ASSIGN_REF, $1, $4); }
1027-
| T_CLONE expr { $$ = zend_ast_create(ZEND_AST_CLONE, $2); }
1030+
| clone { $$ = $1; }
10281031
| variable T_PLUS_EQUAL expr
10291032
{ $$ = zend_ast_create_assign_op(ZEND_ADD, $1, $3); }
10301033
| variable T_MINUS_EQUAL expr
@@ -1471,6 +1474,24 @@ isset_variable:
14711474
expr { $$ = zend_ast_create(ZEND_AST_ISSET, $1); }
14721475
;
14731476

1477+
clone:
1478+
T_CLONE expr { $$ = zend_ast_create(ZEND_AST_CLONE, $2, NULL); }
1479+
| T_CLONE expr T_WITH property_initializer_list { $$ = zend_ast_create(ZEND_AST_CLONE, $2, $4); }
1480+
1481+
property_initializer_list:
1482+
'{' '}' { $$ = zend_ast_create_list(0, ZEND_AST_PROPERTY_INITIALIZER_LIST); }
1483+
| '{' non_empty_property_initializer_list possible_comma '}' { $$ = $2; }
1484+
;
1485+
1486+
non_empty_property_initializer_list:
1487+
property_initializer_expr { $$ = zend_ast_create_list(1, ZEND_AST_PROPERTY_INITIALIZER_LIST, $1); }
1488+
| non_empty_property_initializer_list ',' property_initializer_expr { $$ = zend_ast_list_add($1, $3); }
1489+
;
1490+
1491+
property_initializer_expr:
1492+
identifier ':' expr { $$ = zend_ast_create(ZEND_AST_INITIALIZER_EXPR, $1, $3); }
1493+
;
1494+
14741495
%%
14751496

14761497
/* Over-ride Bison formatting routine to give better token descriptions.

Zend/zend_language_scanner.l

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,6 +1604,10 @@ NEWLINE ("\r"|"\n"|"\r\n")
16041604
RETURN_TOKEN_WITH_IDENT(T_CLONE);
16051605
}
16061606

1607+
<ST_IN_SCRIPTING>"with" {
1608+
RETURN_TOKEN_WITH_IDENT(T_WITH);
1609+
}
1610+
16071611
<ST_IN_SCRIPTING>"var" {
16081612
RETURN_TOKEN_WITH_IDENT(T_VAR);
16091613
}

0 commit comments

Comments
 (0)