Skip to content

Implement "Constructor Promotion" #5291

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Zend/tests/ctor_promotion_abstract.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Constructor promotion cannot be used inside an abstract constructor
--FILE--
<?php

abstract class Test {
abstract public function __construct(public int $x);
}

?>
--EXPECTF--
Fatal error: Cannot declare promoted property in an abstract constructor in %s on line %d
12 changes: 12 additions & 0 deletions Zend/tests/ctor_promotion_additional_modifiers.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Constructor promotion only permits visibility modifiers
--FILE--
<?php

class Test {
public function __construct(public static $x) {}
}

?>
--EXPECTF--
Parse error: syntax error, unexpected 'static' (T_STATIC), expecting variable (T_VARIABLE) in %s on line %d
22 changes: 22 additions & 0 deletions Zend/tests/ctor_promotion_attributes.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
Attributes on promoted properties are assigned to both the property and parameter
--FILE--
<?php

class Test {
public function __construct(
<<NonNegative>>
public int $num,
) {}
}

$prop = new ReflectionProperty(Test::class, 'num');
var_dump($prop->getAttributes()[0]->getName());

$param = new ReflectionParameter([Test::class, '__construct'], 'num');
var_dump($param->getAttributes()[0]->getName());

?>
--EXPECT--
string(11) "NonNegative"
string(11) "NonNegative"
21 changes: 21 additions & 0 deletions Zend/tests/ctor_promotion_basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Constructor promotion (basic example)
--FILE--
<?php

class Point {
public function __construct(public int $x, public int $y, public int $z) {}
}

$point = new Point(1, 2, 3);

// Check that properties really are typed.
try {
$point->x = "foo";
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
Cannot assign string to property Point::$x of type int
20 changes: 20 additions & 0 deletions Zend/tests/ctor_promotion_by_ref.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
Constructor promotion of by-ref parameter
--FILE--
<?php

class Ary {
public function __construct(public array &$array) {}
}

$array = [];
$ary = new Ary($array);
$array[] = 42;
var_dump($ary->array);

?>
--EXPECT--
array(1) {
[0]=>
int(42)
}
12 changes: 12 additions & 0 deletions Zend/tests/ctor_promotion_callable_type.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Type of promoted property may not be callable
--FILE--
<?php

class Test {
public function __construct(public callable $callable) {}
}

?>
--EXPECTF--
Fatal error: Property Test::$callable cannot have type callable in %s on line %d
43 changes: 43 additions & 0 deletions Zend/tests/ctor_promotion_defaults.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
--TEST--
Constructor promotion with default values
--FILE--
<?php

class Point {
public function __construct(
public float $x = 0.0,
public float $y = 1.0,
public float $z = 2.0

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the RFC code example the trailing comma is allowed, but not here.

Which is correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the subject of different RFC

Copy link

@TomasVotruba TomasVotruba Jun 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

) {}
}

var_dump(new Point(10.0));
var_dump(new Point(10.0, 11.0));
var_dump(new Point(10.0, 11.0, 12.0));

?>
--EXPECT--
object(Point)#1 (3) {
["x"]=>
float(10)
["y"]=>
float(1)
["z"]=>
float(2)
}
object(Point)#1 (3) {
["x"]=>
float(10)
["y"]=>
float(11)
["z"]=>
float(2)
}
object(Point)#1 (3) {
["x"]=>
float(10)
["y"]=>
float(11)
["z"]=>
float(12)
}
10 changes: 10 additions & 0 deletions Zend/tests/ctor_promotion_free_function.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
Constructor promotion cannot be used in a free function
--FILE--
<?php

function __construct(public $prop) {}

?>
--EXPECTF--
Fatal error: Cannot declare promoted property outside a constructor in %s on line %d
12 changes: 12 additions & 0 deletions Zend/tests/ctor_promotion_interface.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Constructor promotion cannot be used inside an abstract constructor (interface variant)
--FILE--
<?php

interface Test {
public function __construct(public int $x);
}

?>
--EXPECTF--
Fatal error: Cannot declare promoted property in an abstract constructor in %s on line %d
54 changes: 54 additions & 0 deletions Zend/tests/ctor_promotion_mixing.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
--TEST--
Constructor promotiong mixed with other properties, parameters and code
--FILE--
<?php

class Test {
public string $prop2;

public function __construct(public string $prop1 = "", $param2 = "") {
$this->prop2 = $prop1 . $param2;
}
}

var_dump(new Test("Foo", "Bar"));
echo "\n";
echo new ReflectionClass(Test::class), "\n";

?>
--EXPECTF--
object(Test)#1 (2) {
["prop2"]=>
string(6) "FooBar"
["prop1"]=>
string(3) "Foo"
}

Class [ <user> class Test ] {
@@ %s

- Constants [0] {
}

- Static properties [0] {
}

- Static methods [0] {
}

- Properties [2] {
Property [ public string $prop2 ]
Property [ public string $prop1 ]
}

- Methods [1] {
Method [ <user, ctor> public method __construct ] {
@@ %s

- Parameters [2] {
Parameter #0 [ <optional> string $prop1 = '' ]
Parameter #1 [ <optional> $param2 = '' ]
}
}
}
}
12 changes: 12 additions & 0 deletions Zend/tests/ctor_promotion_not_a_ctor.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Constructor promotion can only be used in constructors ... duh
--FILE--
<?php

class Test {
public function foobar(public int $x, public int $y) {}
}

?>
--EXPECTF--
Fatal error: Cannot declare promoted property outside a constructor in %s on line %d
12 changes: 12 additions & 0 deletions Zend/tests/ctor_promotion_null_default.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Constructor promotion with null default, requires an explicitly nullable type
--FILE--
<?php

class Test {
public function __construct(public int $x = null) {}
}

?>
--EXPECTF--
Fatal error: Cannot use null as default value for parameter $x of type int in %s on line %d
14 changes: 14 additions & 0 deletions Zend/tests/ctor_promotion_repeated_prop.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Clash between promoted and explicit property
--FILE--
<?php

class Test {
public $prop;

public function __construct(public $prop) {}
}

?>
--EXPECTF--
Fatal error: Cannot redeclare Test::$prop in %s on line %d
21 changes: 21 additions & 0 deletions Zend/tests/ctor_promotion_trait.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Constructor promotion can be used inside a trait
--FILE--
<?php

trait Test {
public function __construct(public $prop) {}
}

class Test2 {
use Test;
}

var_dump(new Test2(42));

?>
--EXPECT--
object(Test2)#1 (1) {
["prop"]=>
int(42)
}
12 changes: 12 additions & 0 deletions Zend/tests/ctor_promotion_variadic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Cannot use constructor promotion with variadic parameter
--FILE--
<?php

class Test {
public function __construct(public string ...$strings) {}
}

?>
--EXPECTF--
Fatal error: Cannot declare variadic promoted property in %s on line %d
31 changes: 31 additions & 0 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,37 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_4(zend_ast_kind kind, zend_ast
return ast;
}

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) {
zend_ast *ast;
uint32_t lineno;

ZEND_ASSERT(kind >> ZEND_AST_NUM_CHILDREN_SHIFT == 5);
ast = zend_ast_alloc(zend_ast_size(5));
ast->kind = kind;
ast->attr = 0;
ast->child[0] = child1;
ast->child[1] = child2;
ast->child[2] = child3;
ast->child[3] = child4;
ast->child[4] = child5;
if (child1) {
lineno = zend_ast_get_lineno(child1);
} else if (child2) {
lineno = zend_ast_get_lineno(child2);
} else if (child3) {
lineno = zend_ast_get_lineno(child3);
} else if (child4) {
lineno = zend_ast_get_lineno(child4);
} else if (child5) {
lineno = zend_ast_get_lineno(child5);
} else {
lineno = CG(zend_lineno);
}
ast->lineno = lineno;

return ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_0(zend_ast_kind kind) {
zend_ast *ast;
zend_ast_list *list;
Expand Down
18 changes: 13 additions & 5 deletions Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,9 @@ enum _zend_ast_kind {
/* 4 child nodes */
ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT,
ZEND_AST_FOREACH,
ZEND_AST_PARAM,

/* 5 child nodes */
ZEND_AST_PARAM = 5 << ZEND_AST_NUM_CHILDREN_SHIFT,
};

typedef uint16_t zend_ast_kind;
Expand Down Expand Up @@ -212,19 +214,20 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_class_const_or_name(zend_ast *

#if ZEND_AST_SPEC
# define ZEND_AST_SPEC_CALL(name, ...) \
ZEND_EXPAND_VA(ZEND_AST_SPEC_CALL_(name, __VA_ARGS__, _4, _3, _2, _1, _0)(__VA_ARGS__))
# define ZEND_AST_SPEC_CALL_(name, _, _4, _3, _2, _1, suffix, ...) \
ZEND_EXPAND_VA(ZEND_AST_SPEC_CALL_(name, __VA_ARGS__, _5, _4, _3, _2, _1, _0)(__VA_ARGS__))
# define ZEND_AST_SPEC_CALL_(name, _, _5, _4, _3, _2, _1, suffix, ...) \
name ## suffix
# define ZEND_AST_SPEC_CALL_EX(name, ...) \
ZEND_EXPAND_VA(ZEND_AST_SPEC_CALL_EX_(name, __VA_ARGS__, _4, _3, _2, _1, _0)(__VA_ARGS__))
# define ZEND_AST_SPEC_CALL_EX_(name, _, _5, _4, _3, _2, _1, suffix, ...) \
ZEND_EXPAND_VA(ZEND_AST_SPEC_CALL_EX_(name, __VA_ARGS__, _5, _4, _3, _2, _1, _0)(__VA_ARGS__))
# define ZEND_AST_SPEC_CALL_EX_(name, _, _6, _5, _4, _3, _2, _1, suffix, ...) \
name ## suffix

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_0(zend_ast_kind kind);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_1(zend_ast_kind kind, zend_ast *child);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_2(zend_ast_kind kind, zend_ast *child1, zend_ast *child2);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_3(zend_ast_kind kind, zend_ast *child1, zend_ast *child2, zend_ast *child3);
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);
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);

static zend_always_inline zend_ast * zend_ast_create_ex_0(zend_ast_kind kind, zend_ast_attr attr) {
zend_ast *ast = zend_ast_create_0(kind);
Expand All @@ -251,6 +254,11 @@ static zend_always_inline zend_ast * zend_ast_create_ex_4(zend_ast_kind kind, ze
ast->attr = attr;
return ast;
}
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) {
zend_ast *ast = zend_ast_create_5(kind, child1, child2, child3, child4, child5);
ast->attr = attr;
return ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_0(zend_ast_kind kind);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_1(zend_ast_kind kind, zend_ast *child);
Expand Down
Loading