Commit e5c6456d371 for php.net
commit e5c6456d3716ba8940dea34c181febea06ea9b27
Author: Daniel Scherzer <daniel.e.scherzer@gmail.com>
Date: Mon Nov 3 14:36:57 2025 -0800
Fix GH-20377: emit assignment for all final promoted properties (#20378)
Previously, the assignment op line was only emitted when one of the other flags
allowed for promoted properties (visibility, set visibility, or readonly) was
also used, or when the property had hooks. The property was still added to the
class, but the magical assignment `$this->prop = $prop` was missing. Add that
assignment even when no visibility is explicitly specified, and a test to
confirm the fix.
diff --git a/NEWS b/NEWS
index 2ebd0e05067..d807595f6ff 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,8 @@ PHP NEWS
(ilutov)
. Fixed bug GH-20194 (null offset deprecation not emitted for writes).
(Girgias)
+ . Fixed bug GH-GH-20377 (final promoted properties without explicit visibility
+ not automatically assigned). (DanielEScherzer)
- Opcache:
. Fixed bug GH-20012 (heap buffer overflow in jit). (Arnaud)
diff --git a/Zend/tests/ctor_promotion/ctor_promotion_final.phpt b/Zend/tests/ctor_promotion/ctor_promotion_final.phpt
new file mode 100644
index 00000000000..abfb5b70880
--- /dev/null
+++ b/Zend/tests/ctor_promotion/ctor_promotion_final.phpt
@@ -0,0 +1,23 @@
+--TEST--
+GH-20377: Constructor promotion with a final property without visibility set
+--FILE--
+<?php
+
+class Demo {
+ public function __construct(
+ final string $foo,
+ final public string $bar,
+ ) {}
+}
+
+$d = new Demo("first", "second");
+var_dump($d);
+
+?>
+--EXPECTF--
+object(Demo)#%d (2) {
+ ["foo"]=>
+ string(5) "first"
+ ["bar"]=>
+ string(6) "second"
+}
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 0180e6e8e1c..8be1ee14f48 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -7770,6 +7770,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32
}
}
+ const uint32_t promotion_flags = ZEND_ACC_PPP_MASK | ZEND_ACC_PPP_SET_MASK | ZEND_ACC_READONLY | ZEND_ACC_FINAL;
for (i = 0; i < list->children; ++i) {
zend_ast *param_ast = list->child[i];
zend_ast *type_ast = param_ast->child[0];
@@ -7781,7 +7782,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 | ZEND_ACC_FINAL);
+ uint32_t property_flags = param_ast->attr & promotion_flags;
bool is_promoted = property_flags || hooks_ast;
CG(zend_lineno) = param_ast->lineno;
@@ -8008,7 +8009,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32
zend_ast *param_ast = list->child[i];
zend_ast *hooks_ast = param_ast->child[5];
bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0;
- uint32_t flags = param_ast->attr & (ZEND_ACC_PPP_MASK | ZEND_ACC_PPP_SET_MASK | ZEND_ACC_READONLY);
+ uint32_t flags = param_ast->attr & promotion_flags;
bool is_promoted = flags || hooks_ast;
if (!is_promoted) {
continue;