]> granicus.if.org Git - php/commitdiff
Implement "Constructor Promotion" RFC
authorNikita Popov <nikita.ppv@gmail.com>
Tue, 24 Mar 2020 12:18:28 +0000 (13:18 +0100)
committerNikita Popov <nikita.ppv@gmail.com>
Fri, 5 Jun 2020 13:15:51 +0000 (15:15 +0200)
RFC: https://wiki.php.net/rfc/constructor_promotion

Closes GH-5291.

26 files changed:
UPGRADING
Zend/tests/ctor_promotion_abstract.phpt [new file with mode: 0644]
Zend/tests/ctor_promotion_additional_modifiers.phpt [new file with mode: 0644]
Zend/tests/ctor_promotion_attributes.phpt [new file with mode: 0644]
Zend/tests/ctor_promotion_basic.phpt [new file with mode: 0644]
Zend/tests/ctor_promotion_by_ref.phpt [new file with mode: 0644]
Zend/tests/ctor_promotion_callable_type.phpt [new file with mode: 0644]
Zend/tests/ctor_promotion_defaults.phpt [new file with mode: 0644]
Zend/tests/ctor_promotion_free_function.phpt [new file with mode: 0644]
Zend/tests/ctor_promotion_interface.phpt [new file with mode: 0644]
Zend/tests/ctor_promotion_mixing.phpt [new file with mode: 0644]
Zend/tests/ctor_promotion_not_a_ctor.phpt [new file with mode: 0644]
Zend/tests/ctor_promotion_null_default.phpt [new file with mode: 0644]
Zend/tests/ctor_promotion_repeated_prop.phpt [new file with mode: 0644]
Zend/tests/ctor_promotion_trait.phpt [new file with mode: 0644]
Zend/tests/ctor_promotion_variadic.phpt [new file with mode: 0644]
Zend/zend_ast.c
Zend/zend_ast.h
Zend/zend_compile.c
Zend/zend_compile.h
Zend/zend_language_parser.y
ext/reflection/php_reflection.c
ext/reflection/php_reflection.stub.php
ext/reflection/php_reflection_arginfo.h
ext/reflection/tests/bug71767.phpt
ext/reflection/tests/constructor_promotion.phpt [new file with mode: 0644]

index 527805f07ea62f16b0bc2016d49cf229fb03dcb7..55557863161fd685fd42faae1520e67a75a346f4 100644 (file)
--- a/UPGRADING
+++ b/UPGRADING
@@ -524,6 +524,9 @@ PHP 8.0 UPGRADE NOTES
     RFC: https://wiki.php.net/rfc/mixed_type_v2
   . Added support for Attributes
     RFC: https://wiki.php.net/rfc/attributes_v2
+  . Added support for constructor property promotion (declaring properties in
+    the constructor signature).
+    RFC: https://wiki.php.net/rfc/constructor_promotion
 
 - Date:
   . Added DateTime::createFromInterface() and
diff --git a/Zend/tests/ctor_promotion_abstract.phpt b/Zend/tests/ctor_promotion_abstract.phpt
new file mode 100644 (file)
index 0000000..937e247
--- /dev/null
@@ -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
diff --git a/Zend/tests/ctor_promotion_additional_modifiers.phpt b/Zend/tests/ctor_promotion_additional_modifiers.phpt
new file mode 100644 (file)
index 0000000..fb3b092
--- /dev/null
@@ -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
diff --git a/Zend/tests/ctor_promotion_attributes.phpt b/Zend/tests/ctor_promotion_attributes.phpt
new file mode 100644 (file)
index 0000000..d85d8d3
--- /dev/null
@@ -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"
diff --git a/Zend/tests/ctor_promotion_basic.phpt b/Zend/tests/ctor_promotion_basic.phpt
new file mode 100644 (file)
index 0000000..206f99f
--- /dev/null
@@ -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
diff --git a/Zend/tests/ctor_promotion_by_ref.phpt b/Zend/tests/ctor_promotion_by_ref.phpt
new file mode 100644 (file)
index 0000000..4b07149
--- /dev/null
@@ -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)
+}
diff --git a/Zend/tests/ctor_promotion_callable_type.phpt b/Zend/tests/ctor_promotion_callable_type.phpt
new file mode 100644 (file)
index 0000000..ae10512
--- /dev/null
@@ -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
diff --git a/Zend/tests/ctor_promotion_defaults.phpt b/Zend/tests/ctor_promotion_defaults.phpt
new file mode 100644 (file)
index 0000000..9999e8a
--- /dev/null
@@ -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)
+}
diff --git a/Zend/tests/ctor_promotion_free_function.phpt b/Zend/tests/ctor_promotion_free_function.phpt
new file mode 100644 (file)
index 0000000..1eb84c5
--- /dev/null
@@ -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
diff --git a/Zend/tests/ctor_promotion_interface.phpt b/Zend/tests/ctor_promotion_interface.phpt
new file mode 100644 (file)
index 0000000..7cc856b
--- /dev/null
@@ -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
diff --git a/Zend/tests/ctor_promotion_mixing.phpt b/Zend/tests/ctor_promotion_mixing.phpt
new file mode 100644 (file)
index 0000000..a527654
--- /dev/null
@@ -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 = '' ]
+      }
+    }
+  }
+}
diff --git a/Zend/tests/ctor_promotion_not_a_ctor.phpt b/Zend/tests/ctor_promotion_not_a_ctor.phpt
new file mode 100644 (file)
index 0000000..110ee8a
--- /dev/null
@@ -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
diff --git a/Zend/tests/ctor_promotion_null_default.phpt b/Zend/tests/ctor_promotion_null_default.phpt
new file mode 100644 (file)
index 0000000..61c7b2d
--- /dev/null
@@ -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
diff --git a/Zend/tests/ctor_promotion_repeated_prop.phpt b/Zend/tests/ctor_promotion_repeated_prop.phpt
new file mode 100644 (file)
index 0000000..6f4a9ff
--- /dev/null
@@ -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
diff --git a/Zend/tests/ctor_promotion_trait.phpt b/Zend/tests/ctor_promotion_trait.phpt
new file mode 100644 (file)
index 0000000..4c10915
--- /dev/null
@@ -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)
+}
diff --git a/Zend/tests/ctor_promotion_variadic.phpt b/Zend/tests/ctor_promotion_variadic.phpt
new file mode 100644 (file)
index 0000000..7e1e81c
--- /dev/null
@@ -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
index ba8af9bcdfc8b77e4c659ad3da412d67725de012..b55f06604e723497f7851fb0f60fe7cb8158d768 100644 (file)
@@ -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;
index 0ec922dcea47f0c8b2b83a832e17dbfa4ff66664..825c392a18da4ca31cd2f3769229e896d7ef6066 100644 (file)
@@ -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;
@@ -212,12 +214,12 @@ 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);
@@ -225,6 +227,7 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_1(zend_ast_kind kind, zend_ast
 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);
@@ -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);
index 707d2ac9da319a9d39c9edbc7cf38ee0aa4cc815..83c2573d791dc8251157580252bc6ffea1115c64 100644 (file)
@@ -5787,9 +5787,12 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall
                zend_ast *var_ast = param_ast->child[1];
                zend_ast *default_ast = param_ast->child[2];
                zend_ast *attributes_ast = param_ast->child[3];
+               zend_ast *doc_comment_ast = param_ast->child[4];
                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 +5865,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,
@@ -5891,11 +5894,62 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall
                                zend_alloc_cache_slots(zend_type_get_num_classes(arg_info->type));
                }
 
-               ZEND_TYPE_FULL_MASK(arg_info->type) |= _ZEND_ARG_INFO_FLAGS(is_ref, is_variadic);
+               uint32_t arg_info_flags = _ZEND_ARG_INFO_FLAGS(is_ref, is_variadic)
+                       | (visibility ? _ZEND_IS_PROMOTED_BIT : 0);
+               ZEND_TYPE_FULL_MASK(arg_info->type) |= arg_info_flags;
                if (opcode == ZEND_RECV) {
                        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) {
+                               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_string *doc_comment =
+                               doc_comment_ast ? zend_string_copy(zend_ast_get_str(doc_comment_ast)) : NULL;
+                       zend_property_info *prop = zend_declare_typed_property(
+                               scope, name, &default_value, visibility | ZEND_ACC_PROMOTED, doc_comment, type);
+                       if (attributes_ast) {
+                               zend_compile_attributes(
+                                       &prop->attributes, attributes_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY);
+                       }
+               }
        }
 
        /* These are assigned at the end to avoid uninitialized memory in case of an error */
@@ -5907,6 +5961,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);
+       }
 }
 /* }}} */
 
index 1a53f92e9e6eb673568089d49c972021e415bb69..9a1e5adaafe1c8956d5105f769afdcefeba7531f 100644 (file)
@@ -209,6 +209,9 @@ typedef struct _zend_oparray_context {
 /* Static method or property                              |     |     |     */
 #define ZEND_ACC_STATIC                  (1 <<  4) /*     |  X  |  X  |     */
 /*                                                        |     |     |     */
+/* Promoted property / parameter                          |     |     |     */
+#define ZEND_ACC_PROMOTED                (1 <<  5) /*     |     |  X  |  X  */
+/*                                                        |     |     |     */
 /* Final class or method                                  |     |     |     */
 #define ZEND_ACC_FINAL                   (1 <<  5) /*  X  |  X  |     |     */
 /*                                                        |     |     |     */
@@ -873,8 +876,9 @@ ZEND_API zend_string *zend_type_to_string(zend_type type);
 #define ZEND_FETCH_CLASS_ALLOW_UNLINKED 0x0400
 #define ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED 0x0800
 
-#define ZEND_PARAM_REF      (1<<0)
-#define ZEND_PARAM_VARIADIC (1<<1)
+/* These should not clash with ZEND_ACC_(PUBLIC|PROTECTED|PRIVATE) */
+#define ZEND_PARAM_REF      (1<<3)
+#define ZEND_PARAM_VARIADIC (1<<4)
 
 #define ZEND_NAME_FQ       0
 #define ZEND_NAME_NOT_FQ   1
@@ -937,10 +941,13 @@ ZEND_API zend_string *zend_type_to_string(zend_type type);
 /* The send mode and is_variadic flag are stored as part of zend_type */
 #define _ZEND_SEND_MODE_SHIFT _ZEND_TYPE_EXTRA_FLAGS_SHIFT
 #define _ZEND_IS_VARIADIC_BIT (1 << (_ZEND_TYPE_EXTRA_FLAGS_SHIFT + 2))
+#define _ZEND_IS_PROMOTED_BIT (1 << (_ZEND_TYPE_EXTRA_FLAGS_SHIFT + 3))
 #define ZEND_ARG_SEND_MODE(arg_info) \
        ((ZEND_TYPE_FULL_MASK((arg_info)->type) >> _ZEND_SEND_MODE_SHIFT) & 3)
 #define ZEND_ARG_IS_VARIADIC(arg_info) \
        ((ZEND_TYPE_FULL_MASK((arg_info)->type) & _ZEND_IS_VARIADIC_BIT) != 0)
+#define ZEND_ARG_IS_PROMOTED(arg_info) \
+       ((ZEND_TYPE_FULL_MASK((arg_info)->type) & _ZEND_IS_PROMOTED_BIT) != 0)
 
 #define ZEND_DIM_IS                                    (1 << 0) /* isset fetch needed for null coalesce */
 #define ZEND_DIM_ALTERNATIVE_SYNTAX    (1 << 1) /* deprecated curly brace usage */
index d8d66553c1335411558977f26125a79847b50c92..4fdc05909bf06b9c97bbaaf80b33a8a9365cf01b 100644 (file)
@@ -262,7 +262,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
 %type <ast> attribute_arguments attribute_decl attribute attributes
 
 %type <num> returns_ref function fn is_reference is_variadic variable_modifiers
-%type <num> method_modifiers non_empty_member_modifiers member_modifier
+%type <num> method_modifiers non_empty_member_modifiers member_modifier optional_visibility_modifier
 %type <num> class_modifiers class_modifier use_type backup_fn_flags
 
 %type <ptr> backup_lex_pos
@@ -684,11 +684,22 @@ attributed_parameter:
        |       parameter                               { $$ = $1; }
 ;
 
+optional_visibility_modifier:
+               %empty                                  { $$ = 0; }
+       |       T_PUBLIC                                { $$ = ZEND_ACC_PUBLIC; }
+       |       T_PROTECTED                             { $$ = ZEND_ACC_PROTECTED; }
+       |       T_PRIVATE                               { $$ = ZEND_ACC_PRIVATE; }
+;
+
 parameter:
-               optional_type_without_static is_reference is_variadic T_VARIABLE
-                       { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $2 | $3, $1, $4, NULL, NULL); }
-       |       optional_type_without_static is_reference is_variadic T_VARIABLE '=' expr
-                       { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $2 | $3, $1, $4, $6, NULL); }
+               optional_visibility_modifier optional_type_without_static
+               is_reference is_variadic T_VARIABLE backup_doc_comment
+                       { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $1 | $3 | $4, $2, $5, NULL,
+                                       NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL); }
+       |       optional_visibility_modifier optional_type_without_static
+               is_reference is_variadic T_VARIABLE backup_doc_comment '=' expr
+                       { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $1 | $3 | $4, $2, $5, $8,
+                                       NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL); }
 ;
 
 
@@ -1078,10 +1089,11 @@ inline_function:
                        { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2 | $13, $1, $3,
                                  zend_string_init("{closure}", sizeof("{closure}") - 1, 0),
                                  $5, $7, $11, $8, NULL); CG(extra_fn_flags) = $9; }
-       |       fn returns_ref '(' parameter_list ')' return_type backup_doc_comment T_DOUBLE_ARROW backup_fn_flags backup_lex_pos expr backup_fn_flags
-                       { $$ = zend_ast_create_decl(ZEND_AST_ARROW_FUNC, $2 | $12, $1, $7,
-                                 zend_string_init("{closure}", sizeof("{closure}") - 1, 0), $4, NULL,
-                                 zend_ast_create(ZEND_AST_RETURN, $11), $6, NULL);
+       |       fn returns_ref backup_doc_comment '(' parameter_list ')' return_type
+               T_DOUBLE_ARROW backup_fn_flags backup_lex_pos expr backup_fn_flags
+                       { $$ = zend_ast_create_decl(ZEND_AST_ARROW_FUNC, $2 | $12, $1, $3,
+                                 zend_string_init("{closure}", sizeof("{closure}") - 1, 0), $5, NULL,
+                                 zend_ast_create(ZEND_AST_RETURN, $11), $7, NULL);
                                  ((zend_ast_decl *) $$)->lex_pos = $10;
                                  CG(extra_fn_flags) = $9; }
 ;
index 6d4f257eb4a8c09cf0c08ffba39229a7c2340cda..d986f828f57b782c6708e456348c1b7263084812 100644 (file)
@@ -2870,6 +2870,22 @@ ZEND_METHOD(ReflectionParameter, isVariadic)
 }
 /* }}} */
 
+/* {{{ proto public bool ReflectionParameter::isPromoted()
+   Returns this constructor parameter has been promoted to a property */
+ZEND_METHOD(ReflectionParameter, isPromoted)
+{
+       reflection_object *intern;
+       parameter_reference *param;
+
+       if (zend_parse_parameters_none() == FAILURE) {
+               RETURN_THROWS();
+       }
+       GET_REFLECTION_OBJECT_PTR(param);
+
+       RETVAL_BOOL(ZEND_ARG_IS_PROMOTED(param->arg_info));
+}
+/* }}} */
+
 /* {{{ proto public bool ReflectionType::allowsNull()
   Returns whether parameter MAY be null */
 ZEND_METHOD(ReflectionType, allowsNull)
@@ -5461,6 +5477,14 @@ ZEND_METHOD(ReflectionProperty, isDefault)
 }
 /* }}} */
 
+/* {{{ proto public bool ReflectionProperty::isPromoted()
+   Returns whether this property has been promoted from a constructor */
+ZEND_METHOD(ReflectionProperty, isPromoted)
+{
+       _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_PROMOTED);
+}
+/* }}} */
+
 /* {{{ proto public int ReflectionProperty::getModifiers()
    Returns a bitfield of the access modifiers for this property */
 ZEND_METHOD(ReflectionProperty, getModifiers)
index c27f971c070cb272dcee07b40d3dcd4b08ce58ea..e3d3fee52cf1fd92a63a91bf47ddad96ecf1b712 100644 (file)
@@ -413,6 +413,8 @@ class ReflectionProperty implements Reflector
     /** @return bool */
     public function isDefault() {}
 
+    public function isPromoted(): bool {}
+
     /** @return int */
     public function getModifiers() {}
 
@@ -553,6 +555,8 @@ class ReflectionParameter implements Reflector
     /** @return bool */
     public function isVariadic() {}
 
+    public function isPromoted(): bool {}
+
     /** @return ReflectionAttribute[] */
     public function getAttributes(?string $name = null, int $flags = 0): array {}
 }
index 3dec73ec8b49573ead5c8653cdce16842218b2cb..e5ed4742ff9bfe4a3752b8be00a555482a8c7d34 100644 (file)
@@ -312,6 +312,9 @@ ZEND_END_ARG_INFO()
 
 #define arginfo_class_ReflectionProperty_isDefault arginfo_class_ReflectionFunctionAbstract___clone
 
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProperty_isPromoted, 0, 0, _IS_BOOL, 0)
+ZEND_END_ARG_INFO()
+
 #define arginfo_class_ReflectionProperty_getModifiers arginfo_class_ReflectionFunctionAbstract___clone
 
 #define arginfo_class_ReflectionProperty_getDeclaringClass arginfo_class_ReflectionFunctionAbstract___clone
@@ -324,8 +327,7 @@ ZEND_END_ARG_INFO()
 
 #define arginfo_class_ReflectionProperty_hasType arginfo_class_ReflectionFunctionAbstract___clone
 
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProperty_hasDefaultValue, 0, 0, _IS_BOOL, 0)
-ZEND_END_ARG_INFO()
+#define arginfo_class_ReflectionProperty_hasDefaultValue arginfo_class_ReflectionProperty_isPromoted
 
 #define arginfo_class_ReflectionProperty_getDefaultValue arginfo_class_ReflectionFunctionAbstract___clone
 
@@ -400,6 +402,8 @@ ZEND_END_ARG_INFO()
 
 #define arginfo_class_ReflectionParameter_isVariadic arginfo_class_ReflectionFunctionAbstract___clone
 
+#define arginfo_class_ReflectionParameter_isPromoted arginfo_class_ReflectionProperty_isPromoted
+
 #define arginfo_class_ReflectionParameter_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes
 
 #define arginfo_class_ReflectionType___clone arginfo_class_ReflectionFunctionAbstract___clone
@@ -604,6 +608,7 @@ ZEND_METHOD(ReflectionProperty, isPrivate);
 ZEND_METHOD(ReflectionProperty, isProtected);
 ZEND_METHOD(ReflectionProperty, isStatic);
 ZEND_METHOD(ReflectionProperty, isDefault);
+ZEND_METHOD(ReflectionProperty, isPromoted);
 ZEND_METHOD(ReflectionProperty, getModifiers);
 ZEND_METHOD(ReflectionProperty, getDeclaringClass);
 ZEND_METHOD(ReflectionProperty, getDocComment);
@@ -644,6 +649,7 @@ ZEND_METHOD(ReflectionParameter, getDefaultValue);
 ZEND_METHOD(ReflectionParameter, isDefaultValueConstant);
 ZEND_METHOD(ReflectionParameter, getDefaultValueConstantName);
 ZEND_METHOD(ReflectionParameter, isVariadic);
+ZEND_METHOD(ReflectionParameter, isPromoted);
 ZEND_METHOD(ReflectionParameter, getAttributes);
 ZEND_METHOD(ReflectionType, allowsNull);
 ZEND_METHOD(ReflectionType, __toString);
@@ -851,6 +857,7 @@ static const zend_function_entry class_ReflectionProperty_methods[] = {
        ZEND_ME(ReflectionProperty, isProtected, arginfo_class_ReflectionProperty_isProtected, ZEND_ACC_PUBLIC)
        ZEND_ME(ReflectionProperty, isStatic, arginfo_class_ReflectionProperty_isStatic, ZEND_ACC_PUBLIC)
        ZEND_ME(ReflectionProperty, isDefault, arginfo_class_ReflectionProperty_isDefault, ZEND_ACC_PUBLIC)
+       ZEND_ME(ReflectionProperty, isPromoted, arginfo_class_ReflectionProperty_isPromoted, ZEND_ACC_PUBLIC)
        ZEND_ME(ReflectionProperty, getModifiers, arginfo_class_ReflectionProperty_getModifiers, ZEND_ACC_PUBLIC)
        ZEND_ME(ReflectionProperty, getDeclaringClass, arginfo_class_ReflectionProperty_getDeclaringClass, ZEND_ACC_PUBLIC)
        ZEND_ME(ReflectionProperty, getDocComment, arginfo_class_ReflectionProperty_getDocComment, ZEND_ACC_PUBLIC)
@@ -903,6 +910,7 @@ static const zend_function_entry class_ReflectionParameter_methods[] = {
        ZEND_ME(ReflectionParameter, isDefaultValueConstant, arginfo_class_ReflectionParameter_isDefaultValueConstant, ZEND_ACC_PUBLIC)
        ZEND_ME(ReflectionParameter, getDefaultValueConstantName, arginfo_class_ReflectionParameter_getDefaultValueConstantName, ZEND_ACC_PUBLIC)
        ZEND_ME(ReflectionParameter, isVariadic, arginfo_class_ReflectionParameter_isVariadic, ZEND_ACC_PUBLIC)
+       ZEND_ME(ReflectionParameter, isPromoted, arginfo_class_ReflectionParameter_isPromoted, ZEND_ACC_PUBLIC)
        ZEND_ME(ReflectionParameter, getAttributes, arginfo_class_ReflectionParameter_getAttributes, ZEND_ACC_PUBLIC)
        ZEND_FE_END
 };
index c20073c67a9a247a1144e72df0e8354377fced28..95094290bf3402b2321757753dc21827fb2f71c1 100644 (file)
@@ -27,13 +27,21 @@ $func = function(
 ) {
 };
 
+/** Correct docblock */
+$func2 = fn(
+    /** wrong docblock */
+    $arg
+) => null;
+
 $reflectionFunction = new ReflectionFunction('foo');
 $reflectionClass = new ReflectionClass(Foo::class);
 $reflectionClosure = new ReflectionFunction($func);
+$reflectionArrowFn = new ReflectionFunction($func2);
 
 echo $reflectionFunction->getDocComment() . PHP_EOL;
 echo $reflectionClass->getMethod('bar')->getDocComment() . PHP_EOL;
 echo $reflectionClosure->getDocComment() . PHP_EOL;
+echo $reflectionArrowFn->getDocComment() . PHP_EOL;
 
 echo "Done\n";
 ?>
@@ -41,4 +49,5 @@ echo "Done\n";
 /** Correct docblock */
 /** Correct docblock */
 /** Correct docblock */
+/** Correct docblock */
 Done
diff --git a/ext/reflection/tests/constructor_promotion.phpt b/ext/reflection/tests/constructor_promotion.phpt
new file mode 100644 (file)
index 0000000..d0a09aa
--- /dev/null
@@ -0,0 +1,70 @@
+--TEST--
+Using Reflection on promoted properties
+--FILE--
+<?php
+
+class Test {
+    public $z;
+    public function __construct(
+        public int $x,
+        /** @SomeAnnotation() */
+        public string $y = "123",
+        string $z = "abc",
+    ) {}
+}
+
+$rc = new ReflectionClass(Test::class);
+echo $rc, "\n";
+
+$y = $rc->getProperty('y');
+var_dump($y->isPromoted());
+var_dump($y->getDocComment());
+$z = $rc->getProperty('z');
+var_dump($z->isPromoted());
+
+echo "\n";
+
+$rp = new ReflectionParameter([Test::class, '__construct'], 'y');
+var_dump($rp->isPromoted());
+$rp = new ReflectionParameter([Test::class, '__construct'], 'z');
+var_dump($rp->isPromoted());
+
+?>
+--EXPECTF--
+Class [ <user> class Test ] {
+  @@ %s 3-11
+
+  - Constants [0] {
+  }
+
+  - Static properties [0] {
+  }
+
+  - Static methods [0] {
+  }
+
+  - Properties [3] {
+    Property [ public $z = NULL ]
+    Property [ public int $x ]
+    Property [ public string $y ]
+  }
+
+  - Methods [1] {
+    Method [ <user, ctor> public method __construct ] {
+      @@ %s 5 - 10
+
+      - Parameters [3] {
+        Parameter #0 [ <required> int $x ]
+        Parameter #1 [ <optional> string $y = '123' ]
+        Parameter #2 [ <optional> string $z = 'abc' ]
+      }
+    }
+  }
+}
+
+bool(true)
+string(24) "/** @SomeAnnotation() */"
+bool(false)
+
+bool(true)
+bool(false)