Skip to content

Commit 190bf5f

Browse files
committed
Bind trait props/consts before parent class
This more accurately matches the "copy & paste" semantics described in the documentation. The same could be done for methods. However, abstract trait methods diverge from this behavior, given that a parent method can satisfy trait methods used in the child. In that case, the method is not copied, but the check must be performed after the parent has been bound. For now, just bother with properties and constants. Fixes GH-15753
1 parent fecad54 commit 190bf5f

File tree

3 files changed

+38
-18
lines changed

3 files changed

+38
-18
lines changed

Zend/tests/traits/constant_015.phpt

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,6 @@ class DerivedClass1 extends BaseClass1 {
1515
use TestTrait1;
1616
}
1717

18-
trait TestTrait2 {
19-
public final const Constant = 123;
20-
}
21-
22-
class BaseClass2 {
23-
public final const Constant = 456;
24-
}
25-
26-
class DerivedClass2 extends BaseClass2 {
27-
use TestTrait2;
28-
}
29-
3018
?>
3119
--EXPECTF--
32-
Fatal error: BaseClass2 and TestTrait2 define the same constant (Constant) in the composition of DerivedClass2. However, the definition differs and is considered incompatible. Class was composed in %s on line %d
20+
Fatal error: DerivedClass1::Constant cannot override final constant BaseClass1::Constant in %s on line %d

Zend/tests/traits/gh15753.phpt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
GH-15753: Overriding readonly properties from traits don't allow assignment from the child
3+
--FILE--
4+
<?php
5+
6+
class P {
7+
protected readonly int $prop;
8+
}
9+
10+
trait T {
11+
protected readonly int $prop;
12+
}
13+
14+
class C extends P {
15+
use T;
16+
17+
public function __construct() {
18+
$this->prop = 20;
19+
}
20+
}
21+
22+
$c = new C();
23+
var_dump($c);
24+
25+
?>
26+
--EXPECT--
27+
object(C)#1 (1) {
28+
["prop":protected]=>
29+
int(20)
30+
}

Zend/zend_inheritance.c

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2455,7 +2455,7 @@ static void zend_do_bind_traits(zend_class_entry *ce, zend_class_entry **traits)
24552455
/* complete initialization of trait structures in ce */
24562456
zend_traits_init_trait_structures(ce, traits, &exclude_tables, &aliases);
24572457

2458-
/* first care about all methods to be flattened into the class */
2458+
/* Flatten all methods into the class */
24592459
zend_do_traits_method_binding(ce, traits, exclude_tables, aliases);
24602460

24612461
if (aliases) {
@@ -2465,10 +2465,6 @@ static void zend_do_bind_traits(zend_class_entry *ce, zend_class_entry **traits)
24652465
if (exclude_tables) {
24662466
efree(exclude_tables);
24672467
}
2468-
2469-
/* then flatten the constants and properties into it, to, mostly to notify developer about problems */
2470-
zend_do_traits_constant_binding(ce, traits);
2471-
zend_do_traits_property_binding(ce, traits);
24722468
}
24732469
/* }}} */
24742470

@@ -2990,6 +2986,12 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string
29902986
zend_enum_register_funcs(ce);
29912987
}
29922988

2989+
if (ce->num_traits) {
2990+
/* Bind all constants and properties first, so that parent inheritance treats them as if
2991+
* they were declared in the child class. */
2992+
zend_do_traits_constant_binding(ce, traits_and_interfaces);
2993+
zend_do_traits_property_binding(ce, traits_and_interfaces);
2994+
}
29932995
if (parent) {
29942996
if (!(parent->ce_flags & ZEND_ACC_LINKED)) {
29952997
add_dependency_obligation(ce, parent);

0 commit comments

Comments
 (0)