Skip to content

Commit 064b464

Browse files
committed
Implement "Constructor Promotion" RFC
RFC: https://wiki.php.net/rfc/constructor_promotion Closes GH-5291.
1 parent 91f283a commit 064b464

26 files changed

+564
-22
lines changed

UPGRADING

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,9 @@ PHP 8.0 UPGRADE NOTES
524524
RFC: https://wiki.php.net/rfc/mixed_type_v2
525525
. Added support for Attributes
526526
RFC: https://wiki.php.net/rfc/attributes_v2
527+
. Added support for constructor property promotion (declaring properties in
528+
the constructor signature).
529+
RFC: https://wiki.php.net/rfc/constructor_promotion
527530

528531
- Date:
529532
. Added DateTime::createFromInterface() and
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Constructor promotion cannot be used inside an abstract constructor
3+
--FILE--
4+
<?php
5+
6+
abstract class Test {
7+
abstract public function __construct(public int $x);
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Cannot declare promoted property in an abstract constructor in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Constructor promotion only permits visibility modifiers
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public function __construct(public static $x) {}
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Parse error: syntax error, unexpected 'static' (T_STATIC), expecting variable (T_VARIABLE) in %s on line %d
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
Attributes on promoted properties are assigned to both the property and parameter
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public function __construct(
8+
<<NonNegative>>
9+
public int $num,
10+
) {}
11+
}
12+
13+
$prop = new ReflectionProperty(Test::class, 'num');
14+
var_dump($prop->getAttributes()[0]->getName());
15+
16+
$param = new ReflectionParameter([Test::class, '__construct'], 'num');
17+
var_dump($param->getAttributes()[0]->getName());
18+
19+
?>
20+
--EXPECT--
21+
string(11) "NonNegative"
22+
string(11) "NonNegative"

Zend/tests/ctor_promotion_basic.phpt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Constructor promotion (basic example)
3+
--FILE--
4+
<?php
5+
6+
class Point {
7+
public function __construct(public int $x, public int $y, public int $z) {}
8+
}
9+
10+
$point = new Point(1, 2, 3);
11+
12+
// Check that properties really are typed.
13+
try {
14+
$point->x = "foo";
15+
} catch (TypeError $e) {
16+
echo $e->getMessage(), "\n";
17+
}
18+
19+
?>
20+
--EXPECT--
21+
Cannot assign string to property Point::$x of type int

Zend/tests/ctor_promotion_by_ref.phpt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
Constructor promotion of by-ref parameter
3+
--FILE--
4+
<?php
5+
6+
class Ary {
7+
public function __construct(public array &$array) {}
8+
}
9+
10+
$array = [];
11+
$ary = new Ary($array);
12+
$array[] = 42;
13+
var_dump($ary->array);
14+
15+
?>
16+
--EXPECT--
17+
array(1) {
18+
[0]=>
19+
int(42)
20+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Type of promoted property may not be callable
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public function __construct(public callable $callable) {}
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Property Test::$callable cannot have type callable in %s on line %d
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
--TEST--
2+
Constructor promotion with default values
3+
--FILE--
4+
<?php
5+
6+
class Point {
7+
public function __construct(
8+
public float $x = 0.0,
9+
public float $y = 1.0,
10+
public float $z = 2.0
11+
) {}
12+
}
13+
14+
var_dump(new Point(10.0));
15+
var_dump(new Point(10.0, 11.0));
16+
var_dump(new Point(10.0, 11.0, 12.0));
17+
18+
?>
19+
--EXPECT--
20+
object(Point)#1 (3) {
21+
["x"]=>
22+
float(10)
23+
["y"]=>
24+
float(1)
25+
["z"]=>
26+
float(2)
27+
}
28+
object(Point)#1 (3) {
29+
["x"]=>
30+
float(10)
31+
["y"]=>
32+
float(11)
33+
["z"]=>
34+
float(2)
35+
}
36+
object(Point)#1 (3) {
37+
["x"]=>
38+
float(10)
39+
["y"]=>
40+
float(11)
41+
["z"]=>
42+
float(12)
43+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Constructor promotion cannot be used in a free function
3+
--FILE--
4+
<?php
5+
6+
function __construct(public $prop) {}
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: Cannot declare promoted property outside a constructor in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Constructor promotion cannot be used inside an abstract constructor (interface variant)
3+
--FILE--
4+
<?php
5+
6+
interface Test {
7+
public function __construct(public int $x);
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Cannot declare promoted property in an abstract constructor in %s on line %d

Zend/tests/ctor_promotion_mixing.phpt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
--TEST--
2+
Constructor promotiong mixed with other properties, parameters and code
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public string $prop2;
8+
9+
public function __construct(public string $prop1 = "", $param2 = "") {
10+
$this->prop2 = $prop1 . $param2;
11+
}
12+
}
13+
14+
var_dump(new Test("Foo", "Bar"));
15+
echo "\n";
16+
echo new ReflectionClass(Test::class), "\n";
17+
18+
?>
19+
--EXPECTF--
20+
object(Test)#1 (2) {
21+
["prop2"]=>
22+
string(6) "FooBar"
23+
["prop1"]=>
24+
string(3) "Foo"
25+
}
26+
27+
Class [ <user> class Test ] {
28+
@@ %s
29+
30+
- Constants [0] {
31+
}
32+
33+
- Static properties [0] {
34+
}
35+
36+
- Static methods [0] {
37+
}
38+
39+
- Properties [2] {
40+
Property [ public string $prop2 ]
41+
Property [ public string $prop1 ]
42+
}
43+
44+
- Methods [1] {
45+
Method [ <user, ctor> public method __construct ] {
46+
@@ %s
47+
48+
- Parameters [2] {
49+
Parameter #0 [ <optional> string $prop1 = '' ]
50+
Parameter #1 [ <optional> $param2 = '' ]
51+
}
52+
}
53+
}
54+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Constructor promotion can only be used in constructors ... duh
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public function foobar(public int $x, public int $y) {}
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Cannot declare promoted property outside a constructor in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Constructor promotion with null default, requires an explicitly nullable type
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public function __construct(public int $x = null) {}
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Cannot use null as default value for parameter $x of type int in %s on line %d
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Clash between promoted and explicit property
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public $prop;
8+
9+
public function __construct(public $prop) {}
10+
}
11+
12+
?>
13+
--EXPECTF--
14+
Fatal error: Cannot redeclare Test::$prop in %s on line %d

Zend/tests/ctor_promotion_trait.phpt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Constructor promotion can be used inside a trait
3+
--FILE--
4+
<?php
5+
6+
trait Test {
7+
public function __construct(public $prop) {}
8+
}
9+
10+
class Test2 {
11+
use Test;
12+
}
13+
14+
var_dump(new Test2(42));
15+
16+
?>
17+
--EXPECT--
18+
object(Test2)#1 (1) {
19+
["prop"]=>
20+
int(42)
21+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Cannot use constructor promotion with variadic parameter
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public function __construct(public string ...$strings) {}
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Cannot declare variadic promoted property in %s on line %d

Zend/zend_ast.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,37 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_4(zend_ast_kind kind, zend_ast
244244
return ast;
245245
}
246246

247+
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_5(zend_ast_kind kind, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4, zend_ast *child5) {
248+
zend_ast *ast;
249+
uint32_t lineno;
250+
251+
ZEND_ASSERT(kind >> ZEND_AST_NUM_CHILDREN_SHIFT == 5);
252+
ast = zend_ast_alloc(zend_ast_size(5));
253+
ast->kind = kind;
254+
ast->attr = 0;
255+
ast->child[0] = child1;
256+
ast->child[1] = child2;
257+
ast->child[2] = child3;
258+
ast->child[3] = child4;
259+
ast->child[4] = child5;
260+
if (child1) {
261+
lineno = zend_ast_get_lineno(child1);
262+
} else if (child2) {
263+
lineno = zend_ast_get_lineno(child2);
264+
} else if (child3) {
265+
lineno = zend_ast_get_lineno(child3);
266+
} else if (child4) {
267+
lineno = zend_ast_get_lineno(child4);
268+
} else if (child5) {
269+
lineno = zend_ast_get_lineno(child5);
270+
} else {
271+
lineno = CG(zend_lineno);
272+
}
273+
ast->lineno = lineno;
274+
275+
return ast;
276+
}
277+
247278
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_0(zend_ast_kind kind) {
248279
zend_ast *ast;
249280
zend_ast_list *list;

Zend/zend_ast.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,9 @@ enum _zend_ast_kind {
156156
/* 4 child nodes */
157157
ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT,
158158
ZEND_AST_FOREACH,
159-
ZEND_AST_PARAM,
159+
160+
/* 5 child nodes */
161+
ZEND_AST_PARAM = 5 << ZEND_AST_NUM_CHILDREN_SHIFT,
160162
};
161163

162164
typedef uint16_t zend_ast_kind;
@@ -212,19 +214,20 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_class_const_or_name(zend_ast *
212214

213215
#if ZEND_AST_SPEC
214216
# define ZEND_AST_SPEC_CALL(name, ...) \
215-
ZEND_EXPAND_VA(ZEND_AST_SPEC_CALL_(name, __VA_ARGS__, _4, _3, _2, _1, _0)(__VA_ARGS__))
216-
# define ZEND_AST_SPEC_CALL_(name, _, _4, _3, _2, _1, suffix, ...) \
217+
ZEND_EXPAND_VA(ZEND_AST_SPEC_CALL_(name, __VA_ARGS__, _5, _4, _3, _2, _1, _0)(__VA_ARGS__))
218+
# define ZEND_AST_SPEC_CALL_(name, _, _5, _4, _3, _2, _1, suffix, ...) \
217219
name ## suffix
218220
# define ZEND_AST_SPEC_CALL_EX(name, ...) \
219-
ZEND_EXPAND_VA(ZEND_AST_SPEC_CALL_EX_(name, __VA_ARGS__, _4, _3, _2, _1, _0)(__VA_ARGS__))
220-
# define ZEND_AST_SPEC_CALL_EX_(name, _, _5, _4, _3, _2, _1, suffix, ...) \
221+
ZEND_EXPAND_VA(ZEND_AST_SPEC_CALL_EX_(name, __VA_ARGS__, _5, _4, _3, _2, _1, _0)(__VA_ARGS__))
222+
# define ZEND_AST_SPEC_CALL_EX_(name, _, _6, _5, _4, _3, _2, _1, suffix, ...) \
221223
name ## suffix
222224

223225
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_0(zend_ast_kind kind);
224226
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_1(zend_ast_kind kind, zend_ast *child);
225227
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_2(zend_ast_kind kind, zend_ast *child1, zend_ast *child2);
226228
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_3(zend_ast_kind kind, zend_ast *child1, zend_ast *child2, zend_ast *child3);
227229
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_4(zend_ast_kind kind, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4);
230+
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_5(zend_ast_kind kind, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4, zend_ast *child5);
228231

229232
static zend_always_inline zend_ast * zend_ast_create_ex_0(zend_ast_kind kind, zend_ast_attr attr) {
230233
zend_ast *ast = zend_ast_create_0(kind);
@@ -251,6 +254,11 @@ static zend_always_inline zend_ast * zend_ast_create_ex_4(zend_ast_kind kind, ze
251254
ast->attr = attr;
252255
return ast;
253256
}
257+
static zend_always_inline zend_ast * zend_ast_create_ex_5(zend_ast_kind kind, zend_ast_attr attr, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4, zend_ast *child5) {
258+
zend_ast *ast = zend_ast_create_5(kind, child1, child2, child3, child4, child5);
259+
ast->attr = attr;
260+
return ast;
261+
}
254262

255263
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_0(zend_ast_kind kind);
256264
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_1(zend_ast_kind kind, zend_ast *child);

0 commit comments

Comments
 (0)