Skip to content

Commit 76f7e03

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 c78f0e3 commit 76f7e03

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
@@ -2937,7 +2937,7 @@ static void zend_do_bind_traits(zend_class_entry *ce, zend_class_entry **traits)
29372937
/* complete initialization of trait structures in ce */
29382938
zend_traits_init_trait_structures(ce, traits, &exclude_tables, &aliases);
29392939

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

29432943
if (aliases) {
@@ -2947,10 +2947,6 @@ static void zend_do_bind_traits(zend_class_entry *ce, zend_class_entry **traits)
29472947
if (exclude_tables) {
29482948
efree(exclude_tables);
29492949
}
2950-
2951-
/* then flatten the constants and properties into it, to, mostly to notify developer about problems */
2952-
zend_do_traits_constant_binding(ce, traits);
2953-
zend_do_traits_property_binding(ce, traits);
29542950
}
29552951
/* }}} */
29562952

@@ -3562,6 +3558,12 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string
35623558
zend_enum_register_funcs(ce);
35633559
}
35643560

3561+
if (ce->num_traits) {
3562+
/* Bind all constants and properties first, so that parent inheritance treats them as if
3563+
* they were declared in the child class. */
3564+
zend_do_traits_constant_binding(ce, traits_and_interfaces);
3565+
zend_do_traits_property_binding(ce, traits_and_interfaces);
3566+
}
35653567
if (parent) {
35663568
if (!(parent->ce_flags & ZEND_ACC_LINKED)) {
35673569
add_dependency_obligation(ce, parent);

0 commit comments

Comments
 (0)