-
Notifications
You must be signed in to change notification settings - Fork 8k
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
Changes from 1 commit
a7c7d91
72b1bca
abc2cdf
6470db7
1a9dfaf
a4a52ed
fe75c27
34c0b66
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| 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 |
| 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 |
| 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 |
| 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) | ||
| } |
| 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 |
| 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 | ||
| ) {} | ||
| } | ||
|
|
||
| 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) | ||
| } | ||
| 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 |
| 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 |
| 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 [ <default> public $prop2 ] | ||
| Property [ <default> public $prop1 ] | ||
| } | ||
|
|
||
| - Methods [1] { | ||
| Method [ <user, ctor> public method __construct ] { | ||
| @@ %s | ||
|
|
||
| - Parameters [2] { | ||
| Parameter #0 [ <optional> string $prop1 = '' ] | ||
| Parameter #1 [ <optional> $param2 = '' ] | ||
| } | ||
| } | ||
| } | ||
| } |
| 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 |
| 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 |
| 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 |
| 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) | ||
| } |
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5790,6 +5790,8 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall | |
| zend_string *name = zval_make_interned_string(zend_ast_get_zval(var_ast)); | ||
| zend_bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0; | ||
| zend_bool is_variadic = (param_ast->attr & ZEND_PARAM_VARIADIC) != 0; | ||
| uint32_t visibility = | ||
| param_ast->attr & (ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED|ZEND_ACC_PRIVATE); | ||
|
|
||
| znode var_node, default_node; | ||
| zend_uchar opcode; | ||
|
|
@@ -5862,16 +5864,16 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall | |
|
|
||
| if (type_ast) { | ||
| uint32_t default_type = default_ast ? Z_TYPE(default_node.u.constant) : IS_UNDEF; | ||
| zend_bool force_nullable = default_type == IS_NULL && !visibility; | ||
|
|
||
| op_array->fn_flags |= ZEND_ACC_HAS_TYPE_HINTS; | ||
| arg_info->type = zend_compile_typename( | ||
| type_ast, default_type == IS_NULL, /* use_arena */ 0); | ||
| arg_info->type = zend_compile_typename(type_ast, force_nullable, /* use_arena */ 0); | ||
|
|
||
| if (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_VOID) { | ||
| zend_error_noreturn(E_COMPILE_ERROR, "void cannot be used as a parameter type"); | ||
| } | ||
|
|
||
| if (default_type > IS_NULL && default_type != IS_CONSTANT_AST | ||
| if (default_type != IS_UNDEF && default_type != IS_CONSTANT_AST && !force_nullable | ||
| && !zend_is_valid_default_value(arg_info->type, &default_node.u.constant)) { | ||
| zend_string *type_str = zend_type_to_string(arg_info->type); | ||
| zend_error_noreturn(E_COMPILE_ERROR, | ||
|
|
@@ -5896,6 +5898,49 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall | |
| opline->op2.num = type_ast ? | ||
| ZEND_TYPE_FULL_MASK(arg_info->type) : MAY_BE_ANY; | ||
| } | ||
|
|
||
| if (visibility) { | ||
| zend_op_array *op_array = CG(active_op_array); | ||
| zend_class_entry *scope = op_array->scope; | ||
| zend_bool is_ctor = | ||
| scope && zend_string_equals_literal_ci(op_array->function_name, "__construct"); | ||
| if (!is_ctor) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could add a test of global functions/closures - I'd guess that the following snippet probably has a misleading error message (not sure what scope->properties_info is) <?php
function __construct(public $x) {}LGTM otherwise
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a test in 717134e, error message seems fine. Or did you expect something different there?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah sorry, I missed that you had Still works fine though, because
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, didn't see the |
||
| zend_error_noreturn(E_COMPILE_ERROR, | ||
| "Cannot declare promoted property outside a constructor"); | ||
| } | ||
| if ((op_array->fn_flags & ZEND_ACC_ABSTRACT) | ||
| || (scope->ce_flags & ZEND_ACC_INTERFACE)) { | ||
| zend_error_noreturn(E_COMPILE_ERROR, | ||
| "Cannot declare promoted property in an abstract constructor"); | ||
| } | ||
| if (is_variadic) { | ||
| zend_error_noreturn(E_COMPILE_ERROR, | ||
| "Cannot declare variadic promoted property"); | ||
| } | ||
| if (zend_hash_exists(&scope->properties_info, name)) { | ||
| zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::$%s", | ||
| ZSTR_VAL(scope->name), ZSTR_VAL(name)); | ||
| } | ||
| if (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_CALLABLE) { | ||
| zend_string *str = zend_type_to_string(arg_info->type); | ||
| zend_error_noreturn(E_COMPILE_ERROR, | ||
| "Property %s::$%s cannot have type %s", | ||
| ZSTR_VAL(scope->name), ZSTR_VAL(name), ZSTR_VAL(str)); | ||
| } | ||
|
|
||
| /* Always use uninitialized as the default. */ | ||
| zval default_value; | ||
| ZVAL_UNDEF(&default_value); | ||
|
|
||
| /* Recompile the type, as it has different memory management requirements. */ | ||
| zend_type type = ZEND_TYPE_INIT_NONE(0); | ||
| if (type_ast) { | ||
| type = zend_compile_typename(type_ast, /* force_allow_null */ 0, /* use_arena */ 1); | ||
| } | ||
|
|
||
| zend_declare_typed_property( | ||
| scope, name, &default_value, visibility, /* doc_comment */ NULL, type); | ||
| } | ||
| } | ||
|
|
||
| /* These are assigned at the end to avoid uninitialized memory in case of an error */ | ||
|
|
@@ -5907,6 +5952,29 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall | |
| op_array->num_args--; | ||
| } | ||
| zend_set_function_arg_flags((zend_function*)op_array); | ||
|
|
||
| for (i = 0; i < list->children; i++) { | ||
| zend_ast *param_ast = list->child[i]; | ||
| zend_bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0; | ||
| uint32_t visibility = | ||
| param_ast->attr & (ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED|ZEND_ACC_PRIVATE); | ||
| if (!visibility) { | ||
| continue; | ||
| } | ||
|
|
||
| /* Emit $this->prop = $prop for promoted properties. */ | ||
| zend_string *name = zend_ast_get_str(param_ast->child[1]); | ||
| znode name_node, value_node; | ||
| name_node.op_type = IS_CONST; | ||
| ZVAL_STR_COPY(&name_node.u.constant, name); | ||
| value_node.op_type = IS_CV; | ||
| value_node.u.op.var = lookup_cv(name); | ||
|
|
||
| zend_op *opline = zend_emit_op(NULL, | ||
| is_ref ? ZEND_ASSIGN_OBJ_REF : ZEND_ASSIGN_OBJ, NULL, &name_node); | ||
| opline->extended_value = zend_alloc_cache_slots(3); | ||
| zend_emit_op_data(&value_node); | ||
| } | ||
| } | ||
| /* }}} */ | ||
|
|
||
|
|
||
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, here is PHP Wiki: https://wiki.php.net/rfc/trailing_comma_in_parameter_list