From d126fce3de321fd087ebc4c0123723061caffea4 Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Thu, 18 Jun 2026 00:11:19 +0200 Subject: [PATCH] Primary Constructors RFC Signed-off-by: Robert Landers --- Zend/tests/primary_ctor/attributes.phpt | 31 +++++++++++ Zend/tests/primary_ctor/basic.phpt | 25 +++++++++ Zend/tests/primary_ctor/docblock.phpt | 36 +++++++++++++ .../primary_ctor/error_anon_parent_args.phpt | 9 ++++ Zend/tests/primary_ctor/error_enum_trait.phpt | 18 +++++++ Zend/tests/primary_ctor/error_interface.phpt | 8 +++ .../error_parent_args_no_primary.phpt | 9 ++++ .../error_parent_empty_no_primary.phpt | 9 ++++ .../primary_ctor/error_readonly_hook.phpt | 12 +++++ .../error_redeclare_construct.phpt | 10 ++++ .../hook_cross_parameter_order_edge.phpt | 24 +++++++++ .../hook_cross_parameter_validation.phpt | 33 ++++++++++++ .../primary_ctor/hook_derived_state.phpt | 38 ++++++++++++++ Zend/tests/primary_ctor/hook_validation.phpt | 26 ++++++++++ .../primary_ctor/parent_call_variants.phpt | 23 ++++++++ .../tests/primary_ctor/parent_forwarding.phpt | 31 +++++++++++ .../primary_ctor/promotion_and_bare.phpt | 31 +++++++++++ Zend/tests/primary_ctor/property_order.phpt | 24 +++++++++ Zend/tests/primary_ctor/readonly_class.phpt | 20 +++++++ .../semantic_readonly_with_hooks.phpt | 34 ++++++++++++ Zend/zend_ast.c | 16 +++++- Zend/zend_ast.h | 1 + Zend/zend_compile.c | 52 +++++++++++++++++++ Zend/zend_compile.h | 9 ++++ Zend/zend_language_parser.y | 31 +++++++---- 25 files changed, 549 insertions(+), 11 deletions(-) create mode 100644 Zend/tests/primary_ctor/attributes.phpt create mode 100644 Zend/tests/primary_ctor/basic.phpt create mode 100644 Zend/tests/primary_ctor/docblock.phpt create mode 100644 Zend/tests/primary_ctor/error_anon_parent_args.phpt create mode 100644 Zend/tests/primary_ctor/error_enum_trait.phpt create mode 100644 Zend/tests/primary_ctor/error_interface.phpt create mode 100644 Zend/tests/primary_ctor/error_parent_args_no_primary.phpt create mode 100644 Zend/tests/primary_ctor/error_parent_empty_no_primary.phpt create mode 100644 Zend/tests/primary_ctor/error_readonly_hook.phpt create mode 100644 Zend/tests/primary_ctor/error_redeclare_construct.phpt create mode 100644 Zend/tests/primary_ctor/hook_cross_parameter_order_edge.phpt create mode 100644 Zend/tests/primary_ctor/hook_cross_parameter_validation.phpt create mode 100644 Zend/tests/primary_ctor/hook_derived_state.phpt create mode 100644 Zend/tests/primary_ctor/hook_validation.phpt create mode 100644 Zend/tests/primary_ctor/parent_call_variants.phpt create mode 100644 Zend/tests/primary_ctor/parent_forwarding.phpt create mode 100644 Zend/tests/primary_ctor/promotion_and_bare.phpt create mode 100644 Zend/tests/primary_ctor/property_order.phpt create mode 100644 Zend/tests/primary_ctor/readonly_class.phpt create mode 100644 Zend/tests/primary_ctor/semantic_readonly_with_hooks.phpt diff --git a/Zend/tests/primary_ctor/attributes.phpt b/Zend/tests/primary_ctor/attributes.phpt new file mode 100644 index 000000000000..fae5b1369bc3 --- /dev/null +++ b/Zend/tests/primary_ctor/attributes.phpt @@ -0,0 +1,31 @@ +--TEST-- +Primary constructors: attributes apply to promoted parameters/properties +--FILE-- +getDocComment()); +var_dump($class->getConstructor()->getDocComment()); + +$param = $class->getConstructor()->getParameters()[0]; +var_dump($param->getAttributes()[0]->getName()); +var_dump($param->getAttributes()[0]->newInstance()->name); + +$prop = (new ReflectionProperty(Row::class, 'id'))->getAttributes()[0]; +var_dump($prop->newInstance()->name); +?> +--EXPECT-- +string(21) "/** @param int $id */" +string(21) "/** @param int $id */" +string(3) "Col" +string(2) "id" +string(2) "id" diff --git a/Zend/tests/primary_ctor/basic.phpt b/Zend/tests/primary_ctor/basic.phpt new file mode 100644 index 000000000000..103b1bdab844 --- /dev/null +++ b/Zend/tests/primary_ctor/basic.phpt @@ -0,0 +1,25 @@ +--TEST-- +Primary constructors: basic promotion, defaults, named args +--FILE-- +x + $this->y; } +} + +$p = new Point(3); +var_dump($p->x, $p->y, $p->sum()); + +$p2 = new Point(x: 10, y: 20); +var_dump($p2->sum()); + +$r = new ReflectionMethod(Point::class, '__construct'); +var_dump($r->getNumberOfParameters()); +var_dump($r->isPublic()); +?> +--EXPECT-- +int(3) +int(0) +int(3) +int(30) +int(2) +bool(true) diff --git a/Zend/tests/primary_ctor/docblock.phpt b/Zend/tests/primary_ctor/docblock.phpt new file mode 100644 index 000000000000..c2dc2c1ed852 --- /dev/null +++ b/Zend/tests/primary_ctor/docblock.phpt @@ -0,0 +1,36 @@ +--TEST-- +Primary constructors: a class doc comment also applies to the synthesized constructor +--FILE-- +getDocComment()); +var_dump($rc->getConstructor()->getDocComment()); + +class D(public int $y) {} + +$rd = new ReflectionClass(D::class); +var_dump($rd->getDocComment()); +var_dump($rd->getConstructor()->getDocComment()); + +class E( + /** Doc for z */ public int $z, +) {} + +$re = new ReflectionClass(E::class); +var_dump($re->getDocComment()); +var_dump($re->getConstructor()->getDocComment()); +var_dump($re->getProperty('z')->getDocComment()); +?> +--EXPECT-- +string(16) "/** Doc for C */" +string(16) "/** Doc for C */" +bool(false) +bool(false) +bool(false) +bool(false) +string(16) "/** Doc for z */" +--CREDITS-- +Robert Landers diff --git a/Zend/tests/primary_ctor/error_anon_parent_args.phpt b/Zend/tests/primary_ctor/error_anon_parent_args.phpt new file mode 100644 index 000000000000..21e5637d94ac --- /dev/null +++ b/Zend/tests/primary_ctor/error_anon_parent_args.phpt @@ -0,0 +1,9 @@ +--TEST-- +Primary constructors: anonymous classes cannot forward arguments to the parent +--FILE-- + +--EXPECTF-- +Fatal error: Cannot pass arguments to the parent constructor without a primary constructor in %s on line %d diff --git a/Zend/tests/primary_ctor/error_enum_trait.phpt b/Zend/tests/primary_ctor/error_enum_trait.phpt new file mode 100644 index 000000000000..23d766f8ede0 --- /dev/null +++ b/Zend/tests/primary_ctor/error_enum_trait.phpt @@ -0,0 +1,18 @@ +--TEST-- +Primary constructors: not allowed on enums or traits +--FILE-- +getMessage(), "\n"; + } +} + +tryCompile('enum E(public int $x) { case A; }'); +tryCompile('trait T(public int $x) {}'); +?> +--EXPECT-- +syntax error, unexpected token "(", expecting "{" +syntax error, unexpected token "(", expecting "{" diff --git a/Zend/tests/primary_ctor/error_interface.phpt b/Zend/tests/primary_ctor/error_interface.phpt new file mode 100644 index 000000000000..a12ff4ca5319 --- /dev/null +++ b/Zend/tests/primary_ctor/error_interface.phpt @@ -0,0 +1,8 @@ +--TEST-- +Primary constructors: not allowed on interfaces +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "(", expecting "{" in %s on line %d diff --git a/Zend/tests/primary_ctor/error_parent_args_no_primary.phpt b/Zend/tests/primary_ctor/error_parent_args_no_primary.phpt new file mode 100644 index 000000000000..b44606dfa1c5 --- /dev/null +++ b/Zend/tests/primary_ctor/error_parent_args_no_primary.phpt @@ -0,0 +1,9 @@ +--TEST-- +Primary constructors: parent constructor arguments require a primary constructor +--FILE-- + +--EXPECTF-- +Fatal error: Cannot pass arguments to the parent constructor without a primary constructor in %s on line %d diff --git a/Zend/tests/primary_ctor/error_parent_empty_no_primary.phpt b/Zend/tests/primary_ctor/error_parent_empty_no_primary.phpt new file mode 100644 index 000000000000..ffb8a5c5001f --- /dev/null +++ b/Zend/tests/primary_ctor/error_parent_empty_no_primary.phpt @@ -0,0 +1,9 @@ +--TEST-- +Primary constructors: parent constructor call requires a primary constructor +--FILE-- + +--EXPECTF-- +Fatal error: Cannot call the parent constructor without a primary constructor in %s on line %d diff --git a/Zend/tests/primary_ctor/error_readonly_hook.phpt b/Zend/tests/primary_ctor/error_readonly_hook.phpt new file mode 100644 index 000000000000..8d80967e87d6 --- /dev/null +++ b/Zend/tests/primary_ctor/error_readonly_hook.phpt @@ -0,0 +1,12 @@ +--TEST-- +Primary constructors: hooked promoted properties cannot be readonly +--FILE-- +celsius = $value; } + } +) {} +?> +--EXPECTF-- +Fatal error: Hooked properties cannot be readonly in %s on line %d diff --git a/Zend/tests/primary_ctor/error_redeclare_construct.phpt b/Zend/tests/primary_ctor/error_redeclare_construct.phpt new file mode 100644 index 000000000000..27d47cd22ebf --- /dev/null +++ b/Zend/tests/primary_ctor/error_redeclare_construct.phpt @@ -0,0 +1,10 @@ +--TEST-- +Primary constructors: cannot also declare an explicit __construct() +--FILE-- + +--EXPECTF-- +Fatal error: Cannot redeclare Bad::__construct() in %s on line %d diff --git a/Zend/tests/primary_ctor/hook_cross_parameter_order_edge.phpt b/Zend/tests/primary_ctor/hook_cross_parameter_order_edge.phpt new file mode 100644 index 000000000000..ec2f257eaac0 --- /dev/null +++ b/Zend/tests/primary_ctor/hook_cross_parameter_order_edge.phpt @@ -0,0 +1,24 @@ +--TEST-- +Primary constructors: promoted property hooks cannot read later promoted properties before initialization +--FILE-- +end)); + var_dump($this->end); + $this->start = $value; + } + }, + public int $end, +) {} + +try { + new Range(1, 3); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +bool(false) +Typed property Range::$end must not be accessed before initialization diff --git a/Zend/tests/primary_ctor/hook_cross_parameter_validation.phpt b/Zend/tests/primary_ctor/hook_cross_parameter_validation.phpt new file mode 100644 index 000000000000..f477f2a2a98a --- /dev/null +++ b/Zend/tests/primary_ctor/hook_cross_parameter_validation.phpt @@ -0,0 +1,33 @@ +--TEST-- +Primary constructors: later promoted property hooks can validate against earlier promoted properties +--FILE-- +start > $value) { + throw new ValueError('start must precede end'); + } + $this->end = $value; + } + } +) {} + +$ok = new Range(1, 3); +var_dump($ok); + +try { + new Range(3, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +object(Range)#1 (2) { + ["start"]=> + int(1) + ["end"]=> + int(3) +} +start must precede end diff --git a/Zend/tests/primary_ctor/hook_derived_state.phpt b/Zend/tests/primary_ctor/hook_derived_state.phpt new file mode 100644 index 000000000000..f6e8da177051 --- /dev/null +++ b/Zend/tests/primary_ctor/hook_derived_state.phpt @@ -0,0 +1,38 @@ +--TEST-- +Primary constructors: a later promoted property hook can initialize derived state +--FILE-- +start > $value) { + throw new ValueError('start must precede end'); + } + $this->end = $value; + $this->length = $value - $this->start; + } + } +) { + public readonly int $length; +} + +$range = new Range(2, 5); +var_dump($range); + +try { + $range->length = 99; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +object(Range)#1 (3) { + ["start"]=> + int(2) + ["end"]=> + int(5) + ["length"]=> + int(3) +} +Cannot modify readonly property Range::$length diff --git a/Zend/tests/primary_ctor/hook_validation.phpt b/Zend/tests/primary_ctor/hook_validation.phpt new file mode 100644 index 000000000000..4d68582d0452 --- /dev/null +++ b/Zend/tests/primary_ctor/hook_validation.phpt @@ -0,0 +1,26 @@ +--TEST-- +Primary constructors: a set hook on a promoted parameter validates during construction +--FILE-- +celsius = $value; + } + } +) {} + +var_dump((new Temperature(20.0))->celsius); + +try { + new Temperature(-300.0); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +float(20) +below absolute zero diff --git a/Zend/tests/primary_ctor/parent_call_variants.phpt b/Zend/tests/primary_ctor/parent_call_variants.phpt new file mode 100644 index 000000000000..f67673f8ed70 --- /dev/null +++ b/Zend/tests/primary_ctor/parent_call_variants.phpt @@ -0,0 +1,23 @@ +--TEST-- +Primary constructors: extends Base() calls parent, extends Base (no parens) does not +--FILE-- +v = 7; } +} + +class A(public int $x) extends WithCtor() {} +$a = new A(1); +var_dump($a->x, $a->v); + +class B(public int $x) extends WithCtor {} +$b = new B(2); +var_dump($b->x, $b->v); +?> +--EXPECT-- +WithCtor::__construct +int(1) +int(7) +int(2) +int(0) diff --git a/Zend/tests/primary_ctor/parent_forwarding.phpt b/Zend/tests/primary_ctor/parent_forwarding.phpt new file mode 100644 index 000000000000..9428fc9bd30e --- /dev/null +++ b/Zend/tests/primary_ctor/parent_forwarding.phpt @@ -0,0 +1,31 @@ +--TEST-- +Primary constructors: forward arguments to the parent constructor via extends +--FILE-- +name; } +} + +final class Circle( + public readonly float $radius, + string $name = 'circle', +) extends Shape($name) { + public function area(): float { return $this->radius ** 2 * 3; } +} + +$c = new Circle(2.0); +printf("%s %.1f\n", $c->name(), $c->area()); + +$c2 = new Circle(1.0, 'unit'); +echo $c2->name(), "\n"; + +$rp = (new ReflectionMethod(Circle::class, '__construct'))->getParameters(); +printf("radius promoted=%s, name promoted=%s\n", + $rp[0]->isPromoted() ? 'yes' : 'no', + $rp[1]->isPromoted() ? 'yes' : 'no'); +?> +--EXPECT-- +circle 12.0 +unit +radius promoted=yes, name promoted=no diff --git a/Zend/tests/primary_ctor/promotion_and_bare.phpt b/Zend/tests/primary_ctor/promotion_and_bare.phpt new file mode 100644 index 000000000000..a3a62d91e5da --- /dev/null +++ b/Zend/tests/primary_ctor/promotion_and_bare.phpt @@ -0,0 +1,31 @@ +--TEST-- +Primary constructors: explicit modifier promotes, bare parameter does not +--FILE-- +z; } +} + +$f = new Foo(1, 2); +var_dump(property_exists($f, 'x')); +var_dump(property_exists($f, 'y')); +var_dump(property_exists($f, 'z')); +var_dump($f->y, $f->z()); + +foreach ((new ReflectionMethod(Foo::class, '__construct'))->getParameters() as $p) { + printf("%s promoted=%s\n", $p->getName(), $p->isPromoted() ? 'yes' : 'no'); +} +?> +--EXPECT-- +bool(false) +bool(true) +bool(true) +int(2) +int(9) +x promoted=no +y promoted=yes +z promoted=yes diff --git a/Zend/tests/primary_ctor/property_order.phpt b/Zend/tests/primary_ctor/property_order.phpt new file mode 100644 index 000000000000..93d394f6f9af --- /dev/null +++ b/Zend/tests/primary_ctor/property_order.phpt @@ -0,0 +1,24 @@ +--TEST-- +Primary constructors: promoted properties are declared before body members +--FILE-- +getConstructor()->getNumberOfParameters()); +?> +--EXPECT-- +object(Mixed1)#1 (3) { + ["a"]=> + int(1) + ["b"]=> + int(2) + ["c"]=> + int(3) +} +int(0) diff --git a/Zend/tests/primary_ctor/readonly_class.phpt b/Zend/tests/primary_ctor/readonly_class.phpt new file mode 100644 index 000000000000..2099161c3537 --- /dev/null +++ b/Zend/tests/primary_ctor/readonly_class.phpt @@ -0,0 +1,20 @@ +--TEST-- +Primary constructors: readonly class promotes readonly properties +--FILE-- +amount} {$this->currency}"; } +} + +$m = new Money(500); +echo $m->format(), "\n"; + +try { + $m->amount = 1; +} catch (\Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +500 USD +Cannot modify readonly property Money::$amount diff --git a/Zend/tests/primary_ctor/semantic_readonly_with_hooks.phpt b/Zend/tests/primary_ctor/semantic_readonly_with_hooks.phpt new file mode 100644 index 000000000000..bd0d6d2c9361 --- /dev/null +++ b/Zend/tests/primary_ctor/semantic_readonly_with_hooks.phpt @@ -0,0 +1,34 @@ +--TEST-- +Primary constructors: private(set) hooked properties can be semantically readonly +--FILE-- +celsius = $value; + } + } +) {} + +$t = new Temperature(20.0); +var_dump($t->celsius); + +try { + $t->celsius = 21.0; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +try { + new Temperature(-300.0); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +float(20) +Cannot modify private(set) property Temperature::$celsius from global scope +below absolute zero diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index d3ce419c737e..5c3fe211a8c5 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -566,13 +566,27 @@ static inline bool is_power_of_two(uint32_t n) { ZEND_ATTRIBUTE_NODISCARD ZEND_API zend_ast * ZEND_FASTCALL zend_ast_list_add(zend_ast *ast, zend_ast *op) { zend_ast_list *list = zend_ast_get_list(ast); if (list->children >= 4 && is_power_of_two(list->children)) { - list = zend_ast_realloc(list, + list = zend_ast_realloc(list, zend_ast_list_size(list->children), zend_ast_list_size(list->children * 2)); } list->child[list->children++] = op; return (zend_ast *) list; } +ZEND_ATTRIBUTE_NODISCARD ZEND_API zend_ast * ZEND_FASTCALL zend_ast_list_prepend(zend_ast *ast, zend_ast *op) { + zend_ast_list *list = zend_ast_get_list(ast); + if (list->children >= 4 && is_power_of_two(list->children)) { + list = zend_ast_realloc(list, + zend_ast_list_size(list->children), zend_ast_list_size(list->children * 2)); + } + for (uint32_t i = list->children; i > 0; i--) { + list->child[i] = list->child[i - 1]; + } + list->child[0] = op; + list->children++; + return (zend_ast *) list; +} + ZEND_API zend_ast * ZEND_FASTCALL zend_ast_arg_list_add(zend_ast *list, zend_ast *arg) { if (list->kind == ZEND_AST_CALLABLE_CONVERT) { diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 24b77d7d3493..dde080ae79c1 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -329,6 +329,7 @@ ZEND_API zend_ast *zend_ast_create_arg_list(uint32_t init_children, zend_ast_kin #endif ZEND_ATTRIBUTE_NODISCARD ZEND_API zend_ast * ZEND_FASTCALL zend_ast_list_add(zend_ast *list, zend_ast *op); +ZEND_ATTRIBUTE_NODISCARD ZEND_API zend_ast * ZEND_FASTCALL zend_ast_list_prepend(zend_ast *list, zend_ast *op); /* Like zend_ast_list_add(), but wraps the list into a ZEND_AST_CALLABLE_CONVERT * if any arg is a ZEND_AST_PLACEHOLDER_ARG. list can be a zend_ast_list, or a diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 9f8ebad8ab29..bcd93c32a1bf 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1018,6 +1018,58 @@ uint32_t zend_add_class_modifier(uint32_t flags, uint32_t new_flag) /* {{{ */ } /* }}} */ +static zend_ast *zend_ast_prepend_primary_ctor(zend_ast *stmt_list, zend_ast *params, zend_ast *parent_args, uint32_t lineno, zend_string *doc_comment) +{ + zend_ast *body; + zend_string *construct_name = ZSTR_INIT_LITERAL("__construct", 0); + + if (parent_args) { + zval parent_zv, method_zv; + ZVAL_INTERNED_STR(&parent_zv, ZSTR_KNOWN(ZEND_STR_PARENT)); + ZVAL_STR(&method_zv, zend_string_copy(construct_name)); + zend_ast *class_ast = zend_ast_create_zval_ex(&parent_zv, ZEND_NAME_NOT_FQ); + zend_ast *method_ast = zend_ast_create_zval(&method_zv); + zend_ast *call_ast = zend_ast_create(ZEND_AST_STATIC_CALL, class_ast, method_ast, parent_args); + body = zend_ast_create_list(1, ZEND_AST_STMT_LIST, call_ast); + } else { + body = zend_ast_create_list(0, ZEND_AST_STMT_LIST); + } + + zend_ast *ctor_ast = zend_ast_create_decl( + ZEND_AST_METHOD, ZEND_ACC_PUBLIC, lineno, doc_comment, + construct_name, params, NULL, body, NULL, NULL); + + return zend_ast_list_prepend(stmt_list, ctor_ast); +} + +zend_ast *zend_ast_create_class_decl( + uint32_t flags, uint32_t start_lineno, zend_string *doc_comment, zend_string *name, + zend_ast *extends, zend_ast *parent_args, zend_ast *implements, + zend_ast *params, zend_ast *stmts) +{ + bool invalid_parent_ctor_call = false; + + if (params) { + stmts = zend_ast_prepend_primary_ctor(stmts, params, parent_args, start_lineno, + doc_comment ? zend_string_copy(doc_comment) : NULL); + } else if (parent_args) { + const char *message = zend_ast_get_list(parent_args)->children + ? "Cannot pass arguments to the parent constructor without a primary constructor" + : "Cannot call the parent constructor without a primary constructor"; + zend_throw_exception(zend_ce_compile_error, message, 0); + zend_ast_destroy(parent_args); + invalid_parent_ctor_call = true; + } + + zend_ast *ast = zend_ast_create_decl(ZEND_AST_CLASS, flags, start_lineno, doc_comment, name, + extends, implements, stmts, NULL, NULL); + if (UNEXPECTED(invalid_parent_ctor_call)) { + zend_ast_destroy(ast); + return NULL; + } + return ast; +} + uint32_t zend_add_anonymous_class_modifier(uint32_t flags, uint32_t new_flag) { uint32_t new_flags = flags | new_flag; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 2351882a560d..b847fb079ede 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -121,12 +121,18 @@ typedef struct _zend_file_context { HashTable seen_symbols; } zend_file_context; +typedef struct _zend_extends_info { + zend_ast *class_name; + zend_ast *ctor_args; +} zend_extends_info; + typedef union _zend_parser_stack_elem { zend_ast *ast; zend_string *str; zend_ulong num; unsigned char *ptr; unsigned char *ident; + zend_extends_info extends_info; } zend_parser_stack_elem; void zend_compile_top_stmt(zend_ast *ast); @@ -930,6 +936,9 @@ typedef enum { /* Used during AST construction */ zend_ast *zend_ast_append_str(zend_ast *left, zend_ast *right); zend_ast *zend_negate_num_string(zend_ast *ast); +zend_ast *zend_ast_create_class_decl(uint32_t flags, uint32_t start_lineno, zend_string *doc_comment, + zend_string *name, zend_ast *extends, zend_ast *parent_args, zend_ast *implements, + zend_ast *params, zend_ast *stmts); uint32_t zend_add_class_modifier(uint32_t flags, uint32_t new_flag); uint32_t zend_add_anonymous_class_modifier(uint32_t flags, uint32_t new_flag); uint32_t zend_add_member_modifier(uint32_t flags, uint32_t new_flag, zend_modifier_target target); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index b4dda00404ea..2f63da1c5934 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -48,6 +48,10 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %expect 0 %destructor { zend_ast_destroy($$); } +%destructor { + zend_ast_destroy($$.class_name); + zend_ast_destroy($$.ctor_args); +} %destructor { if ($$) zend_string_release_ex($$, 0); } %precedence T_THROW @@ -258,7 +262,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type unprefixed_use_declarations const_decl inner_statement %type expr optional_expr while_statement for_statement foreach_variable %type foreach_statement declare_statement finally_statement unset_variable variable -%type extends_from parameter optional_type_without_static argument argument_no_expr global_var +%type parameter optional_type_without_static argument argument_no_expr global_var +%type extends_from %type static_var class_statement trait_adaptation trait_precedence trait_alias %type absolute_trait_method_reference trait_method_reference property echo_expr %type new_dereferenceable new_non_dereferenceable anonymous_class class_name class_name_reference simple_variable @@ -602,11 +607,11 @@ is_variadic: class_declaration_statement: class_modifiers T_CLASS { $$ = CG(zend_lineno); } - T_STRING extends_from implements_list backup_doc_comment '{' class_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_CLASS, $1, $3, $7, zend_ast_get_str($4), $5, $6, $9, NULL, NULL); } + T_STRING backup_doc_comment optional_parameter_list extends_from implements_list '{' class_statement_list '}' + { $$ = zend_ast_create_class_decl($1, $3, $5, zend_ast_get_str($4), $7.class_name, $7.ctor_args, $8, $6, $10); $7.class_name = $7.ctor_args = NULL; if (!$$) { YYERROR; } } | T_CLASS { $$ = CG(zend_lineno); } - T_STRING extends_from implements_list backup_doc_comment '{' class_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_CLASS, 0, $2, $6, zend_ast_get_str($3), $4, $5, $8, NULL, NULL); } + T_STRING backup_doc_comment optional_parameter_list extends_from implements_list '{' class_statement_list '}' + { $$ = zend_ast_create_class_decl(0, $2, $4, zend_ast_get_str($3), $6.class_name, $6.ctor_args, $7, $5, $9); $6.class_name = $6.ctor_args = NULL; if (!$$) { YYERROR; } } ; class_modifiers: @@ -667,8 +672,12 @@ enum_case_expr: ; extends_from: - %empty { $$ = NULL; } - | T_EXTENDS class_name { $$ = $2; } + %empty + { $$.class_name = NULL; $$.ctor_args = NULL; } + | T_EXTENDS class_name + { $$.class_name = $2; $$.ctor_args = NULL; } + | T_EXTENDS class_name argument_list + { $$.class_name = $2; $$.ctor_args = $3; } ; interface_extends_list: @@ -1228,9 +1237,11 @@ non_empty_for_exprs: anonymous_class: anonymous_class_modifiers_optional T_CLASS { $$ = CG(zend_lineno); } ctor_arguments extends_from implements_list backup_doc_comment '{' class_statement_list '}' { - zend_ast *decl = zend_ast_create_decl( - ZEND_AST_CLASS, ZEND_ACC_ANON_CLASS | $1, $3, $7, NULL, - $5, $6, $9, NULL, NULL); + zend_ast *decl = zend_ast_create_class_decl( + ZEND_ACC_ANON_CLASS | $1, $3, $7, NULL, + $5.class_name, $5.ctor_args, $6, NULL, $9); + $5.class_name = $5.ctor_args = NULL; + if (!decl) { zend_ast_destroy($4); YYERROR; } $$ = zend_ast_create(ZEND_AST_NEW, decl, $4); } ;