From 8199ec9bbe79729a57659e7e079153d40ecd9eae Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 13 Nov 2025 19:58:22 -0800 Subject: [PATCH 1/2] [RFC] Allow `#[\Override]` on class constants --- .../with_Override_error_constant.phpt | 15 +++++++ .../with_Override_okay.phpt | 4 +- .../override/constants/anon_failure.phpt | 15 +++++++ .../override/constants/anon_interface.phpt | 19 +++++++++ .../override/constants/anon_parent.phpt | 19 +++++++++ .../attributes/override/constants/basic.phpt | 39 +++++++++++++++++++ .../override/constants/failure.phpt | 15 +++++++ .../override/constants/interface_failure.phpt | 15 +++++++ .../override/constants/trait_failure.phpt | 19 +++++++++ .../override/constants/trait_interface.phpt | 23 +++++++++++ .../override/constants/trait_parent.phpt | 23 +++++++++++ .../override/constants/trait_redeclared.phpt | 21 ++++++++++ .../constants/trait_redeclared_interface.phpt | 25 ++++++++++++ .../constants/trait_redeclared_parent.phpt | 25 ++++++++++++ .../override/constants/trait_unused.phpt | 15 +++++++ .../override/constants/visibility_01.phpt | 19 +++++++++ .../override/constants/visibility_02.phpt | 19 +++++++++ .../override/constants/visibility_03.phpt | 19 +++++++++ .../override/constants/visibility_04.phpt | 19 +++++++++ Zend/zend_attributes.stub.php | 2 +- Zend/zend_attributes_arginfo.h | 4 +- Zend/zend_compile.c | 8 ++++ Zend/zend_inheritance.c | 13 +++++++ 23 files changed, 391 insertions(+), 4 deletions(-) create mode 100644 Zend/tests/attributes/delayed_target_validation/with_Override_error_constant.phpt create mode 100644 Zend/tests/attributes/override/constants/anon_failure.phpt create mode 100644 Zend/tests/attributes/override/constants/anon_interface.phpt create mode 100644 Zend/tests/attributes/override/constants/anon_parent.phpt create mode 100644 Zend/tests/attributes/override/constants/basic.phpt create mode 100644 Zend/tests/attributes/override/constants/failure.phpt create mode 100644 Zend/tests/attributes/override/constants/interface_failure.phpt create mode 100644 Zend/tests/attributes/override/constants/trait_failure.phpt create mode 100644 Zend/tests/attributes/override/constants/trait_interface.phpt create mode 100644 Zend/tests/attributes/override/constants/trait_parent.phpt create mode 100644 Zend/tests/attributes/override/constants/trait_redeclared.phpt create mode 100644 Zend/tests/attributes/override/constants/trait_redeclared_interface.phpt create mode 100644 Zend/tests/attributes/override/constants/trait_redeclared_parent.phpt create mode 100644 Zend/tests/attributes/override/constants/trait_unused.phpt create mode 100644 Zend/tests/attributes/override/constants/visibility_01.phpt create mode 100644 Zend/tests/attributes/override/constants/visibility_02.phpt create mode 100644 Zend/tests/attributes/override/constants/visibility_03.phpt create mode 100644 Zend/tests/attributes/override/constants/visibility_04.phpt diff --git a/Zend/tests/attributes/delayed_target_validation/with_Override_error_constant.phpt b/Zend/tests/attributes/delayed_target_validation/with_Override_error_constant.phpt new file mode 100644 index 0000000000000..9ebb8e4c02025 --- /dev/null +++ b/Zend/tests/attributes/delayed_target_validation/with_Override_error_constant.phpt @@ -0,0 +1,15 @@ +--TEST-- +#[\DelayedTargetValidation] with #[\Override]: non-overrides still error (class constant) +--FILE-- + +--EXPECTF-- +Fatal error: DemoClass::CLASS_CONSTANT has #[\Override] attribute, but no matching parent constant exists in %s on line %d diff --git a/Zend/tests/attributes/delayed_target_validation/with_Override_okay.phpt b/Zend/tests/attributes/delayed_target_validation/with_Override_okay.phpt index dd077f4b9cbd3..494d85eaea9cd 100644 --- a/Zend/tests/attributes/delayed_target_validation/with_Override_okay.phpt +++ b/Zend/tests/attributes/delayed_target_validation/with_Override_okay.phpt @@ -12,6 +12,8 @@ class Base { set => $value; } + public const CLASS_CONST = ''; + public function printVal() { echo __METHOD__ . "\n"; } @@ -34,7 +36,7 @@ class DemoClass extends Base { } #[DelayedTargetValidation] - #[Override] // Does nothing here + #[Override] // Does something here public const CLASS_CONST = 'FOO'; public function __construct( diff --git a/Zend/tests/attributes/override/constants/anon_failure.phpt b/Zend/tests/attributes/override/constants/anon_failure.phpt new file mode 100644 index 0000000000000..c43cedda81d7d --- /dev/null +++ b/Zend/tests/attributes/override/constants/anon_failure.phpt @@ -0,0 +1,15 @@ +--TEST-- +#[\Override]: Constants - anonymous class, no interface or parent class +--FILE-- + +--EXPECTF-- +Fatal error: class@anonymous::C has #[\Override] attribute, but no matching parent constant exists in %s on line %d diff --git a/Zend/tests/attributes/override/constants/anon_interface.phpt b/Zend/tests/attributes/override/constants/anon_interface.phpt new file mode 100644 index 0000000000000..b19abfbf4d3a4 --- /dev/null +++ b/Zend/tests/attributes/override/constants/anon_interface.phpt @@ -0,0 +1,19 @@ +--TEST-- +#[\Override]: Constants - anonymous class overrides interface +--FILE-- + +--EXPECT-- +Done diff --git a/Zend/tests/attributes/override/constants/anon_parent.phpt b/Zend/tests/attributes/override/constants/anon_parent.phpt new file mode 100644 index 0000000000000..b430705c3c9d2 --- /dev/null +++ b/Zend/tests/attributes/override/constants/anon_parent.phpt @@ -0,0 +1,19 @@ +--TEST-- +#[\Override]: Constants - anonymous class overrides parent class +--FILE-- + +--EXPECT-- +Done diff --git a/Zend/tests/attributes/override/constants/basic.phpt b/Zend/tests/attributes/override/constants/basic.phpt new file mode 100644 index 0000000000000..4453ef0a787d4 --- /dev/null +++ b/Zend/tests/attributes/override/constants/basic.phpt @@ -0,0 +1,39 @@ +--TEST-- +#[\Override]: Constants - basic +--FILE-- + +--EXPECT-- +Done diff --git a/Zend/tests/attributes/override/constants/failure.phpt b/Zend/tests/attributes/override/constants/failure.phpt new file mode 100644 index 0000000000000..be621915d9278 --- /dev/null +++ b/Zend/tests/attributes/override/constants/failure.phpt @@ -0,0 +1,15 @@ +--TEST-- +#[\Override]: Constants - no interface or parent class +--FILE-- + +--EXPECTF-- +Fatal error: Demo::C has #[\Override] attribute, but no matching parent constant exists in %s on line %d diff --git a/Zend/tests/attributes/override/constants/interface_failure.phpt b/Zend/tests/attributes/override/constants/interface_failure.phpt new file mode 100644 index 0000000000000..0aa4d33d1301d --- /dev/null +++ b/Zend/tests/attributes/override/constants/interface_failure.phpt @@ -0,0 +1,15 @@ +--TEST-- +#[\Override]: Constants - no parent interface +--FILE-- + +--EXPECTF-- +Fatal error: IFace::I has #[\Override] attribute, but no matching parent constant exists in %s on line %d diff --git a/Zend/tests/attributes/override/constants/trait_failure.phpt b/Zend/tests/attributes/override/constants/trait_failure.phpt new file mode 100644 index 0000000000000..8d4af0e57977d --- /dev/null +++ b/Zend/tests/attributes/override/constants/trait_failure.phpt @@ -0,0 +1,19 @@ +--TEST-- +#[\Override]: Constants - on a trait, no interface or parent class +--FILE-- + +--EXPECTF-- +Fatal error: UsesTrait::T has #[\Override] attribute, but no matching parent constant exists in %s on line %d diff --git a/Zend/tests/attributes/override/constants/trait_interface.phpt b/Zend/tests/attributes/override/constants/trait_interface.phpt new file mode 100644 index 0000000000000..3a2ba799ae4e6 --- /dev/null +++ b/Zend/tests/attributes/override/constants/trait_interface.phpt @@ -0,0 +1,23 @@ +--TEST-- +#[\Override]: Constants - on a trait, overrides interface +--FILE-- + +--EXPECT-- +Done diff --git a/Zend/tests/attributes/override/constants/trait_parent.phpt b/Zend/tests/attributes/override/constants/trait_parent.phpt new file mode 100644 index 0000000000000..2753aaf546a2b --- /dev/null +++ b/Zend/tests/attributes/override/constants/trait_parent.phpt @@ -0,0 +1,23 @@ +--TEST-- +#[\Override]: Constants - on a trait, overrides parent class +--FILE-- + +--EXPECT-- +Done diff --git a/Zend/tests/attributes/override/constants/trait_redeclared.phpt b/Zend/tests/attributes/override/constants/trait_redeclared.phpt new file mode 100644 index 0000000000000..984f9eedb6684 --- /dev/null +++ b/Zend/tests/attributes/override/constants/trait_redeclared.phpt @@ -0,0 +1,21 @@ +--TEST-- +#[\Override]: Constants - trait constant redeclared, not overridden +--FILE-- + +--EXPECTF-- +Fatal error: UsesTrait::T has #[\Override] attribute, but no matching parent constant exists in %s on line %d diff --git a/Zend/tests/attributes/override/constants/trait_redeclared_interface.phpt b/Zend/tests/attributes/override/constants/trait_redeclared_interface.phpt new file mode 100644 index 0000000000000..21a1aa532249a --- /dev/null +++ b/Zend/tests/attributes/override/constants/trait_redeclared_interface.phpt @@ -0,0 +1,25 @@ +--TEST-- +#[\Override]: Constants - trait constant redeclared, overrides interface +--FILE-- + +--EXPECT-- +Done diff --git a/Zend/tests/attributes/override/constants/trait_redeclared_parent.phpt b/Zend/tests/attributes/override/constants/trait_redeclared_parent.phpt new file mode 100644 index 0000000000000..c3d1e43d25348 --- /dev/null +++ b/Zend/tests/attributes/override/constants/trait_redeclared_parent.phpt @@ -0,0 +1,25 @@ +--TEST-- +#[\Override]: Constants - trait constant redeclared, overrides parent class +--FILE-- + +--EXPECT-- +Done diff --git a/Zend/tests/attributes/override/constants/trait_unused.phpt b/Zend/tests/attributes/override/constants/trait_unused.phpt new file mode 100644 index 0000000000000..785f2888e90bd --- /dev/null +++ b/Zend/tests/attributes/override/constants/trait_unused.phpt @@ -0,0 +1,15 @@ +--TEST-- +#[\Override]: Constants - on a trait, unused +--FILE-- + +--EXPECT-- +Done diff --git a/Zend/tests/attributes/override/constants/visibility_01.phpt b/Zend/tests/attributes/override/constants/visibility_01.phpt new file mode 100644 index 0000000000000..49671471f7582 --- /dev/null +++ b/Zend/tests/attributes/override/constants/visibility_01.phpt @@ -0,0 +1,19 @@ +--TEST-- +#[\Override]: Constants - private constant not overridden (by public constant) +--FILE-- + +--EXPECTF-- +Fatal error: Child::C has #[\Override] attribute, but no matching parent constant exists in %s on line %d diff --git a/Zend/tests/attributes/override/constants/visibility_02.phpt b/Zend/tests/attributes/override/constants/visibility_02.phpt new file mode 100644 index 0000000000000..c160a29a6180a --- /dev/null +++ b/Zend/tests/attributes/override/constants/visibility_02.phpt @@ -0,0 +1,19 @@ +--TEST-- +#[\Override]: Constants - private constant not overridden (by private constant) +--FILE-- + +--EXPECTF-- +Fatal error: Child::C has #[\Override] attribute, but no matching parent constant exists in %s on line %d diff --git a/Zend/tests/attributes/override/constants/visibility_03.phpt b/Zend/tests/attributes/override/constants/visibility_03.phpt new file mode 100644 index 0000000000000..db460f24375e3 --- /dev/null +++ b/Zend/tests/attributes/override/constants/visibility_03.phpt @@ -0,0 +1,19 @@ +--TEST-- +#[\Override]: Constants - protected constant is overridden (by public constant) +--FILE-- + +--EXPECT-- +Done diff --git a/Zend/tests/attributes/override/constants/visibility_04.phpt b/Zend/tests/attributes/override/constants/visibility_04.phpt new file mode 100644 index 0000000000000..0ad46b022f434 --- /dev/null +++ b/Zend/tests/attributes/override/constants/visibility_04.phpt @@ -0,0 +1,19 @@ +--TEST-- +#[\Override]: Constants - protected constant is overridden (by protected constant) +--FILE-- + +--EXPECT-- +Done diff --git a/Zend/zend_attributes.stub.php b/Zend/zend_attributes.stub.php index ded9c89593a36..05fd285d5b9f1 100644 --- a/Zend/zend_attributes.stub.php +++ b/Zend/zend_attributes.stub.php @@ -68,7 +68,7 @@ public function __debugInfo(): array {} /** * @strict-properties */ -#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_PROPERTY)] +#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_PROPERTY|Attribute::TARGET_CLASS_CONSTANT)] final class Override { public function __construct() {} diff --git a/Zend/zend_attributes_arginfo.h b/Zend/zend_attributes_arginfo.h index ec8d8de4ee508..75d008f654442 100644 --- a/Zend/zend_attributes_arginfo.h +++ b/Zend/zend_attributes_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: b868cb33f41d9442f42d0cec84e33fcc09f5d88c */ + * Stub hash: bae42fcb945360f90a1e2762c6c22177f25efb9e */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Attribute___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "Attribute::TARGET_ALL") @@ -230,7 +230,7 @@ static zend_class_entry *register_class_Override(void) zend_string *attribute_name_Attribute_class_Override_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, true); zend_attribute *attribute_Attribute_class_Override_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_Override_0, 1); zend_string_release_ex(attribute_name_Attribute_class_Override_0, true); - ZVAL_LONG(&attribute_Attribute_class_Override_0->args[0].value, ZEND_ATTRIBUTE_TARGET_METHOD | ZEND_ATTRIBUTE_TARGET_PROPERTY); + ZVAL_LONG(&attribute_Attribute_class_Override_0->args[0].value, ZEND_ATTRIBUTE_TARGET_METHOD | ZEND_ATTRIBUTE_TARGET_PROPERTY | ZEND_ATTRIBUTE_TARGET_CLASS_CONST); return class_entry; } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 50ba8029873ad..92b1d00a36d3f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -9161,6 +9161,14 @@ static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_as ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS; ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; } + + const zend_attribute *override = zend_get_attribute_str(c->attributes, "override", sizeof("override") - 1); + if (override) { + ZEND_CLASS_CONST_FLAGS(c) |= ZEND_ACC_OVERRIDE; + /* We need to be able to remove the flag */ + ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS; + ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; + } } } } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 1f128764bdd3d..cb624ed2db6b4 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2127,6 +2127,10 @@ static bool do_inherit_constant_check( ); } + if (!(ZEND_CLASS_CONST_FLAGS(parent_constant) & ZEND_ACC_PRIVATE)) { + ZEND_CLASS_CONST_FLAGS(child_constant) &= ~ZEND_ACC_OVERRIDE; + } + if (!(ZEND_CLASS_CONST_FLAGS(parent_constant) & ZEND_ACC_PRIVATE) && ZEND_TYPE_IS_SET(parent_constant->type)) { inheritance_status status = class_constant_types_compatible(parent_constant, child_constant); if (status == INHERITANCE_ERROR) { @@ -2334,6 +2338,15 @@ void zend_inheritance_check_override(const zend_class_entry *ce) } } ZEND_HASH_FOREACH_END(); + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->constants_table, zend_string *name, zend_class_constant *c) { + if (ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_OVERRIDE) { + zend_error_noreturn( + E_COMPILE_ERROR, + "%s::%s has #[\\Override] attribute, but no matching parent constant exists", + ZSTR_VAL(ce->name), ZSTR_VAL(name)); + } + } ZEND_HASH_FOREACH_END(); + ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, zend_property_info *prop) { if (prop->flags & ZEND_ACC_OVERRIDE) { zend_error_noreturn( From f5fdc15b6cf47dae598450d5d81e7d07e0291214 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 13 Nov 2025 20:29:32 -0800 Subject: [PATCH 2/2] Only on exact class --- Zend/zend_inheritance.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index cb624ed2db6b4..f09f5dafb0ba6 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2128,7 +2128,9 @@ static bool do_inherit_constant_check( } if (!(ZEND_CLASS_CONST_FLAGS(parent_constant) & ZEND_ACC_PRIVATE)) { - ZEND_CLASS_CONST_FLAGS(child_constant) &= ~ZEND_ACC_OVERRIDE; + if (child_constant->ce == ce) { + ZEND_CLASS_CONST_FLAGS(child_constant) &= ~ZEND_ACC_OVERRIDE; + } } if (!(ZEND_CLASS_CONST_FLAGS(parent_constant) & ZEND_ACC_PRIVATE) && ZEND_TYPE_IS_SET(parent_constant->type)) {