Skip to content

Commit 9a7a394

Browse files
committed
Add support for property initialization during cloning
1 parent 7edf034 commit 9a7a394

23 files changed

+1733
-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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Test that the clone property initializer respects visibility
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
private $bar;
9+
}
10+
11+
$foo = new Foo();
12+
13+
try {
14+
$foo = clone $foo with {bar: 1};
15+
} catch (Error $exception) {
16+
echo $exception->getMessage() . "\n";
17+
}
18+
19+
?>
20+
--EXPECT--
21+
Cannot access private property Foo::$bar
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Test that the "clone with" doesn't support expressions as property names
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public $bar;
9+
}
10+
11+
$property = "bar";
12+
13+
$foo = new Foo();
14+
$foo = clone $foo with {$property: 1};
15+
var_dump($foo);
16+
17+
?>
18+
--EXPECTF--
19+
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+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Test that the "clone with" is still chainable
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public $bar = 1;
9+
10+
public function withProperties()
11+
{
12+
return (clone $this with {})->bar;
13+
}
14+
}
15+
16+
$foo = new Foo();
17+
var_dump($foo);
18+
$bar = $foo->withProperties();
19+
var_dump($bar);
20+
21+
?>
22+
--EXPECT--
23+
object(Foo)#1 (1) {
24+
["bar"]=>
25+
int(1)
26+
}
27+
int(1)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Test that the "clone with" works with dynamic properties
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
}
9+
10+
$foo = new Foo();
11+
$foo = clone $foo with {bar: 1, baz: ""};
12+
var_dump($foo);
13+
14+
?>
15+
--EXPECT--
16+
object(Foo)#2 (2) {
17+
["bar"]=>
18+
int(1)
19+
["baz"]=>
20+
string(0) ""
21+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Test that the "clone with" works with expressions as property values
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public $bar;
9+
public $baz;
10+
}
11+
12+
$foo = new Foo();
13+
$foo = clone $foo with {bar: new stdClass(), baz: strpos("abc", "b")};
14+
var_dump($foo);
15+
16+
?>
17+
--EXPECT--
18+
object(Foo)#2 (2) {
19+
["bar"]=>
20+
object(stdClass)#3 (0) {
21+
}
22+
["baz"]=>
23+
int(1)
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

0 commit comments

Comments
 (0)