From bd6067b2fc3ad196ea8e2c63369ee161faea6360 Mon Sep 17 00:00:00 2001 From: Gabriel Leite Bessa Date: Fri, 30 Oct 2026 02:40:24 -0300 Subject: [PATCH] Fix GH-16643: Improve incompatible trait property error message When a class and a trait (or two traits) define the same property in an incompatible way, the error message now states why the definitions are considered incompatible: differing visibility, static or readonly modifiers, types, or default values, including the case where only one of them has a default value (e.g. a constructor promoted property). Co-Authored-By: Claude Opus 5.5 --- .../readonly_trait_mismatch.phpt | 2 +- .../readonly_with_default_trait_mismatch.phpt | 2 +- Zend/tests/traits/bug60369.phpt | 2 +- Zend/tests/traits/bug60536_002.phpt | 2 +- Zend/tests/traits/bug74269.phpt | 2 +- ...ding-conflicting-property-initializer.phpt | 2 +- Zend/tests/traits/gh16643.phpt | 18 +++++++++++ Zend/tests/traits/property003.phpt | 2 +- Zend/tests/traits/property004.phpt | 2 +- Zend/tests/traits/property005.phpt | 2 +- .../typed_properties_085.phpt | 2 +- Zend/zend_inheritance.c | 30 ++++++++++++------- 12 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 Zend/tests/traits/gh16643.phpt diff --git a/Zend/tests/readonly_props/readonly_trait_mismatch.phpt b/Zend/tests/readonly_props/readonly_trait_mismatch.phpt index 99fc6b5bf8cc..c8b3957b8ee4 100644 --- a/Zend/tests/readonly_props/readonly_trait_mismatch.phpt +++ b/Zend/tests/readonly_props/readonly_trait_mismatch.phpt @@ -15,4 +15,4 @@ class C { ?> --EXPECTF-- -Fatal error: T1 and T2 define the same property ($prop) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d +Fatal error: T1 and T2 define the same property ($prop) in the composition of C. However, the definition differs and is considered incompatible: only one of them is readonly. Class was composed in %s on line %d diff --git a/Zend/tests/readonly_props/readonly_with_default_trait_mismatch.phpt b/Zend/tests/readonly_props/readonly_with_default_trait_mismatch.phpt index 6668b1d860bb..2d62cd852d0b 100644 --- a/Zend/tests/readonly_props/readonly_with_default_trait_mismatch.phpt +++ b/Zend/tests/readonly_props/readonly_with_default_trait_mismatch.phpt @@ -17,4 +17,4 @@ class C { ?> --EXPECTF-- -Fatal error: T1 and T2 define the same property ($prop) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d +Fatal error: T1 and T2 define the same property ($prop) in the composition of C. However, the definition differs and is considered incompatible: the default values differ. Class was composed in %s on line %d diff --git a/Zend/tests/traits/bug60369.phpt b/Zend/tests/traits/bug60369.phpt index 77a706723219..a5592799a0ba 100644 --- a/Zend/tests/traits/bug60369.phpt +++ b/Zend/tests/traits/bug60369.phpt @@ -14,4 +14,4 @@ class Properties { ?> --EXPECTF-- -Fatal error: Properties and PropertiesTrait define the same property ($same) in the composition of Properties. However, the definition differs and is considered incompatible. Class was composed in %s on line %d +Fatal error: Properties and PropertiesTrait define the same property ($same) in the composition of Properties. However, the definition differs and is considered incompatible: only one of them is static. Class was composed in %s on line %d diff --git a/Zend/tests/traits/bug60536_002.phpt b/Zend/tests/traits/bug60536_002.phpt index 4d49caa28bda..1de2bb2399b1 100644 --- a/Zend/tests/traits/bug60536_002.phpt +++ b/Zend/tests/traits/bug60536_002.phpt @@ -34,4 +34,4 @@ $t->hello = "foo"; PRE-CLASS-GUARD POST-CLASS-GUARD -Fatal error: TraitsTest and THello1 define the same property ($hello) in the composition of TraitsTest. However, the definition differs and is considered incompatible. Class was composed in %s on line %d +Fatal error: TraitsTest and THello1 define the same property ($hello) in the composition of TraitsTest. However, the definition differs and is considered incompatible: the visibility differs. Class was composed in %s on line %d diff --git a/Zend/tests/traits/bug74269.phpt b/Zend/tests/traits/bug74269.phpt index 53ef570fb6c4..6ead03a21d04 100644 --- a/Zend/tests/traits/bug74269.phpt +++ b/Zend/tests/traits/bug74269.phpt @@ -14,4 +14,4 @@ class PropertiesExample } ?> --EXPECTF-- -Fatal error: PropertiesExample and PropertiesTrait define the same property ($same) in the composition of PropertiesExample. However, the definition differs and is considered incompatible. Class was composed in %s +Fatal error: PropertiesExample and PropertiesTrait define the same property ($same) in the composition of PropertiesExample. However, the definition differs and is considered incompatible: the default values differ. Class was composed in %s diff --git a/Zend/tests/traits/bugs/overridding-conflicting-property-initializer.phpt b/Zend/tests/traits/bugs/overridding-conflicting-property-initializer.phpt index 5c54f0027a22..36715eda4807 100644 --- a/Zend/tests/traits/bugs/overridding-conflicting-property-initializer.phpt +++ b/Zend/tests/traits/bugs/overridding-conflicting-property-initializer.phpt @@ -20,4 +20,4 @@ $obj = new baz(); echo $obj->zoo, "\n"; ?> --EXPECTF-- -Fatal error: baz and foo define the same property ($zoo) in the composition of baz. However, the definition differs and is considered incompatible. Class was composed in %s on line %d +Fatal error: baz and foo define the same property ($zoo) in the composition of baz. However, the definition differs and is considered incompatible: the default values differ. Class was composed in %s on line %d diff --git a/Zend/tests/traits/gh16643.phpt b/Zend/tests/traits/gh16643.phpt new file mode 100644 index 000000000000..cea22d89c0e9 --- /dev/null +++ b/Zend/tests/traits/gh16643.phpt @@ -0,0 +1,18 @@ +--TEST-- +GH-16643: Incompatible trait property error message states the reason +--FILE-- + +--EXPECTF-- +Fatal error: Foo and FooTrait define the same property ($options) in the composition of Foo. However, the definition differs and is considered incompatible: only one of them has a default value. Class was composed in %s on line %d diff --git a/Zend/tests/traits/property003.phpt b/Zend/tests/traits/property003.phpt index 881edb53f20a..989dc5b731ce 100644 --- a/Zend/tests/traits/property003.phpt +++ b/Zend/tests/traits/property003.phpt @@ -26,4 +26,4 @@ $t->hello = "foo"; --EXPECTF-- PRE-CLASS-GUARD -Fatal error: THello1 and THello2 define the same property ($hello) in the composition of TraitsTest. However, the definition differs and is considered incompatible. Class was composed in %s on line %d +Fatal error: THello1 and THello2 define the same property ($hello) in the composition of TraitsTest. However, the definition differs and is considered incompatible: the visibility differs. Class was composed in %s on line %d diff --git a/Zend/tests/traits/property004.phpt b/Zend/tests/traits/property004.phpt index 5734b83e2b87..c1d11067fe6b 100644 --- a/Zend/tests/traits/property004.phpt +++ b/Zend/tests/traits/property004.phpt @@ -26,4 +26,4 @@ $t = new TraitsTest; --EXPECTF-- PRE-CLASS-GUARD -Fatal error: THello1 and THello2 define the same property ($hello) in the composition of TraitsTest. However, the definition differs and is considered incompatible. Class was composed in %s on line %d +Fatal error: THello1 and THello2 define the same property ($hello) in the composition of TraitsTest. However, the definition differs and is considered incompatible: the visibility differs. Class was composed in %s on line %d diff --git a/Zend/tests/traits/property005.phpt b/Zend/tests/traits/property005.phpt index d0a976cb3768..260dc70a939b 100644 --- a/Zend/tests/traits/property005.phpt +++ b/Zend/tests/traits/property005.phpt @@ -34,4 +34,4 @@ $t->hello = "foo"; PRE-CLASS-GUARD POST-CLASS-GUARD -Fatal error: TraitsTest and THello1 define the same property ($hello) in the composition of TraitsTest. However, the definition differs and is considered incompatible. Class was composed in %s on line %d +Fatal error: TraitsTest and THello1 define the same property ($hello) in the composition of TraitsTest. However, the definition differs and is considered incompatible: the visibility differs. Class was composed in %s on line %d diff --git a/Zend/tests/type_declarations/typed_properties_085.phpt b/Zend/tests/type_declarations/typed_properties_085.phpt index ccac3768cb1c..9c0aa4c02f66 100644 --- a/Zend/tests/type_declarations/typed_properties_085.phpt +++ b/Zend/tests/type_declarations/typed_properties_085.phpt @@ -14,4 +14,4 @@ class C { } ?> --EXPECTF-- -Fatal error: T1 and T2 define the same property ($prop) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d +Fatal error: T1 and T2 define the same property ($prop) in the composition of C. However, the definition differs and is considered incompatible: the types differ. Class was composed in %s on line %d diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 4f78fb6b10d8..14e61334b1ac 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2909,8 +2909,7 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent zend_hash_del(&ce->properties_info, prop_name); flags |= ZEND_ACC_CHANGED; } else { - bool is_compatible = false; - uint32_t flags_mask = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC | ZEND_ACC_READONLY; + const char *reason = NULL; if (colliding_prop->hooks || property_info->hooks) { zend_error_noreturn(E_COMPILE_ERROR, @@ -2921,10 +2920,16 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent ce->name); } - if ((colliding_prop->flags & flags_mask) == (flags & flags_mask) && - verify_property_type_compatibility(property_info, colliding_prop, PROP_INVARIANT, false, false) == INHERITANCE_SUCCESS - ) { - /* the flags are identical, thus, the properties may be compatible */ + if ((colliding_prop->flags & ZEND_ACC_PPP_MASK) != (flags & ZEND_ACC_PPP_MASK)) { + reason = "the visibility differs"; + } else if ((colliding_prop->flags & ZEND_ACC_STATIC) != (flags & ZEND_ACC_STATIC)) { + reason = "only one of them is static"; + } else if ((colliding_prop->flags & ZEND_ACC_READONLY) != (flags & ZEND_ACC_READONLY)) { + reason = "only one of them is readonly"; + } else if (verify_property_type_compatibility(property_info, colliding_prop, PROP_INVARIANT, false, false) != INHERITANCE_SUCCESS) { + reason = "the types differ"; + } else { + /* the flags and types are identical, thus, the properties may be compatible */ zval *op1, *op2; if (flags & ZEND_ACC_STATIC) { @@ -2936,16 +2941,21 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent op1 = &ce->default_properties_table[OBJ_PROP_TO_NUM(colliding_prop->offset)]; op2 = &traits[i]->default_properties_table[OBJ_PROP_TO_NUM(property_info->offset)]; } - is_compatible = check_trait_property_or_constant_value_compatibility(ce, op1, op2); + if ((Z_TYPE_P(op1) == IS_UNDEF) != (Z_TYPE_P(op2) == IS_UNDEF)) { + reason = "only one of them has a default value"; + } else if (!check_trait_property_or_constant_value_compatibility(ce, op1, op2)) { + reason = "the default values differ"; + } } - if (!is_compatible) { + if (reason) { zend_error_noreturn(E_COMPILE_ERROR, - "%pS and %pS define the same property ($%pS) in the composition of %pS. However, the definition differs and is considered incompatible. Class was composed", + "%pS and %pS define the same property ($%pS) in the composition of %pS. However, the definition differs and is considered incompatible: %s. Class was composed", find_first_property_definition(ce, traits, i, prop_name, colliding_prop->ce)->name, property_info->ce->name, prop_name, - ce->name); + ce->name, + reason); } continue; }