From cca1e8d10bac0ad5642cd3762caaa7abf00cb440 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 24 Mar 2025 11:24:51 -0700 Subject: [PATCH 1/4] [RFC] Final Property Promotion https://wiki.php.net/rfc/final_promotion --- UPGRADING | 2 ++ .../property_hooks/final_prop_promoted_1.phpt | 18 ++++++++++++++++++ .../property_hooks/final_prop_promoted_2.phpt | 18 ++++++++++++++++++ .../property_hooks/final_prop_promoted_3.phpt | 18 ++++++++++++++++++ .../final_prop_promoted_ast.phpt | 18 ++++++++++++++++++ Zend/zend_ast.c | 3 +++ Zend/zend_compile.c | 10 ++-------- .../tests/ReflectionProperty_isFinal.phpt | 3 +++ 8 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 Zend/tests/property_hooks/final_prop_promoted_1.phpt create mode 100644 Zend/tests/property_hooks/final_prop_promoted_2.phpt create mode 100644 Zend/tests/property_hooks/final_prop_promoted_3.phpt create mode 100644 Zend/tests/property_hooks/final_prop_promoted_ast.phpt diff --git a/UPGRADING b/UPGRADING index a9bb61fddd86c..5c780a84fee5f 100644 --- a/UPGRADING +++ b/UPGRADING @@ -146,6 +146,8 @@ PHP 8.5 UPGRADE NOTES RFC: https://wiki.php.net/rfc/attributes-on-constants . Added the pipe (|>) operator. RFC: https://wiki.php.net/rfc/pipe-operator-v3 + . Constructor property promotion can now be used for final properties. + RFC: https://wiki.php.net/rfc/final_promotion - Curl: . Added support for share handles that are persisted across multiple PHP diff --git a/Zend/tests/property_hooks/final_prop_promoted_1.phpt b/Zend/tests/property_hooks/final_prop_promoted_1.phpt new file mode 100644 index 0000000000000..740588d8aa2ae --- /dev/null +++ b/Zend/tests/property_hooks/final_prop_promoted_1.phpt @@ -0,0 +1,18 @@ +--TEST-- +Promoted property may be marked final (hook) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot override final property A::$prop in %s on line %d diff --git a/Zend/tests/property_hooks/final_prop_promoted_2.phpt b/Zend/tests/property_hooks/final_prop_promoted_2.phpt new file mode 100644 index 0000000000000..535a7ceabd317 --- /dev/null +++ b/Zend/tests/property_hooks/final_prop_promoted_2.phpt @@ -0,0 +1,18 @@ +--TEST-- +Promoted property may be marked final (normal) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot override final property A::$prop in %s on line %d diff --git a/Zend/tests/property_hooks/final_prop_promoted_3.phpt b/Zend/tests/property_hooks/final_prop_promoted_3.phpt new file mode 100644 index 0000000000000..600d18df84c9a --- /dev/null +++ b/Zend/tests/property_hooks/final_prop_promoted_3.phpt @@ -0,0 +1,18 @@ +--TEST-- +Promoted property may be marked final (no visibility needed) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot override final property A::$prop in %s on line %d diff --git a/Zend/tests/property_hooks/final_prop_promoted_ast.phpt b/Zend/tests/property_hooks/final_prop_promoted_ast.phpt new file mode 100644 index 0000000000000..4b3745c8f9fbc --- /dev/null +++ b/Zend/tests/property_hooks/final_prop_promoted_ast.phpt @@ -0,0 +1,18 @@ +--TEST-- +Confirm that the AST indicates final promoted properties +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECT-- +assert(false && new class { + public function __construct(public final $prop) { + } + +}) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index beecf51216a94..cdc86faa95aa3 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2795,6 +2795,9 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio zend_ast_export_attributes(str, ast->child[3], indent, 0); } zend_ast_export_visibility(str, ast->attr, ZEND_MODIFIER_TARGET_CPP); + if (ast->attr & ZEND_ACC_FINAL) { + smart_str_appends(str, "final "); + } if (ast->child[0]) { zend_ast_export_type(str, ast->child[0], indent); smart_str_appendc(str, ' '); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2bc0cf7b703d9..28bea1a21d759 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -903,13 +903,7 @@ uint32_t zend_modifier_token_to_flag(zend_modifier_target target, uint32_t token } break; case T_FINAL: - if (target == ZEND_MODIFIER_TARGET_METHOD - || target == ZEND_MODIFIER_TARGET_CONSTANT - || target == ZEND_MODIFIER_TARGET_PROPERTY - || target == ZEND_MODIFIER_TARGET_PROPERTY_HOOK) { - return ZEND_ACC_FINAL; - } - break; + return ZEND_ACC_FINAL; case T_STATIC: if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_METHOD) { return ZEND_ACC_STATIC; @@ -7681,7 +7675,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 zend_string *name = zval_make_interned_string(zend_ast_get_zval(var_ast)); bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0; bool is_variadic = (param_ast->attr & ZEND_PARAM_VARIADIC) != 0; - uint32_t property_flags = param_ast->attr & (ZEND_ACC_PPP_MASK | ZEND_ACC_PPP_SET_MASK | ZEND_ACC_READONLY); + uint32_t property_flags = param_ast->attr & (ZEND_ACC_PPP_MASK | ZEND_ACC_PPP_SET_MASK | ZEND_ACC_READONLY | ZEND_ACC_FINAL); bool is_promoted = property_flags || hooks_ast; znode var_node, default_node; diff --git a/ext/reflection/tests/ReflectionProperty_isFinal.phpt b/ext/reflection/tests/ReflectionProperty_isFinal.phpt index 62b792fd7253f..4e2ddda918ec4 100644 --- a/ext/reflection/tests/ReflectionProperty_isFinal.phpt +++ b/ext/reflection/tests/ReflectionProperty_isFinal.phpt @@ -12,6 +12,8 @@ class C { public protected(set) final mixed $p6; public private(set) mixed $p7; public private(set) final mixed $p8; + + public function __construct( final $p9 ) {} } $rc = new ReflectionClass(C::class); @@ -30,3 +32,4 @@ p5: bool(false) p6: bool(true) p7: bool(true) p8: bool(true) +p9: bool(true) From 6475a3907d9534d789147010531681cea408fffb Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Fri, 20 Jun 2025 13:46:11 -0700 Subject: [PATCH 2/4] Add a few more tests --- .../property_hooks/final_prop_promoted_4.phpt | 18 ++++++++++++ .../property_hooks/final_prop_promoted_5.phpt | 28 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 Zend/tests/property_hooks/final_prop_promoted_4.phpt create mode 100644 Zend/tests/property_hooks/final_prop_promoted_5.phpt diff --git a/Zend/tests/property_hooks/final_prop_promoted_4.phpt b/Zend/tests/property_hooks/final_prop_promoted_4.phpt new file mode 100644 index 0000000000000..2b8270ff5a062 --- /dev/null +++ b/Zend/tests/property_hooks/final_prop_promoted_4.phpt @@ -0,0 +1,18 @@ +--TEST-- +Final promoted property conflicts with non-promoted non-hooked property +--FILE-- + +--EXPECTF-- +Fatal error: Cannot override final property A::$prop in %s on line %d diff --git a/Zend/tests/property_hooks/final_prop_promoted_5.phpt b/Zend/tests/property_hooks/final_prop_promoted_5.phpt new file mode 100644 index 0000000000000..438a39be31c03 --- /dev/null +++ b/Zend/tests/property_hooks/final_prop_promoted_5.phpt @@ -0,0 +1,28 @@ +--TEST-- +Non-promoted constructor parameter does not conflict with final promoted property +--FILE-- + +--EXPECT-- +B::__construct(): test +A::__construct(): test From b541af178d36809bb5d67ce0326477422578af9d Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Fri, 20 Jun 2025 13:48:05 -0700 Subject: [PATCH 3/4] Another ReflectionProperty::isFinal() case --- ext/reflection/tests/ReflectionProperty_isFinal.phpt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/reflection/tests/ReflectionProperty_isFinal.phpt b/ext/reflection/tests/ReflectionProperty_isFinal.phpt index 4e2ddda918ec4..102b212711694 100644 --- a/ext/reflection/tests/ReflectionProperty_isFinal.phpt +++ b/ext/reflection/tests/ReflectionProperty_isFinal.phpt @@ -13,7 +13,7 @@ class C { public private(set) mixed $p7; public private(set) final mixed $p8; - public function __construct( final $p9 ) {} + public function __construct( final $p9, public $p10 ) {} } $rc = new ReflectionClass(C::class); @@ -33,3 +33,4 @@ p6: bool(true) p7: bool(true) p8: bool(true) p9: bool(true) +p10: bool(false) From 1967a7be07dbeea31983be5504e8213bc51ded1e Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 21 Jun 2025 13:33:41 -0700 Subject: [PATCH 4/4] Remove extra spaces --- Zend/tests/property_hooks/final_prop_promoted_5.phpt | 2 +- Zend/tests/property_hooks/final_prop_promoted_ast.phpt | 2 +- ext/reflection/tests/ReflectionProperty_isFinal.phpt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Zend/tests/property_hooks/final_prop_promoted_5.phpt b/Zend/tests/property_hooks/final_prop_promoted_5.phpt index 438a39be31c03..3e592f99b3416 100644 --- a/Zend/tests/property_hooks/final_prop_promoted_5.phpt +++ b/Zend/tests/property_hooks/final_prop_promoted_5.phpt @@ -20,7 +20,7 @@ class B extends A { } } -$b = new B("test" ); +$b = new B("test"); ?> --EXPECT-- diff --git a/Zend/tests/property_hooks/final_prop_promoted_ast.phpt b/Zend/tests/property_hooks/final_prop_promoted_ast.phpt index 4b3745c8f9fbc..32aa1f27dc323 100644 --- a/Zend/tests/property_hooks/final_prop_promoted_ast.phpt +++ b/Zend/tests/property_hooks/final_prop_promoted_ast.phpt @@ -4,7 +4,7 @@ Confirm that the AST indicates final promoted properties getMessage(), "\n"; diff --git a/ext/reflection/tests/ReflectionProperty_isFinal.phpt b/ext/reflection/tests/ReflectionProperty_isFinal.phpt index 102b212711694..f01835efbed25 100644 --- a/ext/reflection/tests/ReflectionProperty_isFinal.phpt +++ b/ext/reflection/tests/ReflectionProperty_isFinal.phpt @@ -13,7 +13,7 @@ class C { public private(set) mixed $p7; public private(set) final mixed $p8; - public function __construct( final $p9, public $p10 ) {} + public function __construct(final $p9, public $p10) {} } $rc = new ReflectionClass(C::class);