Skip to content

Commit 7c4db15

Browse files
committed
Merge branch 'PHP-8.3'
* PHP-8.3: Fix #[Override] on traits overriding a parent method without a matching interface (php#12205)
2 parents 7aea6dd + d344fe0 commit 7c4db15

File tree

7 files changed

+179
-10
lines changed

7 files changed

+179
-10
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
#[Override] attribute in trait does not check for parent class implementations
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
public function foo(): void {}
8+
}
9+
10+
interface I {
11+
public function foo(): void;
12+
}
13+
14+
trait T {
15+
#[\Override]
16+
public function foo(): void {
17+
echo 'foo';
18+
}
19+
}
20+
21+
// Works fine
22+
class B implements I {
23+
use T;
24+
}
25+
26+
// Works fine ("copied and pasted into the target class")
27+
class C extends A {
28+
#[\Override]
29+
public function foo(): void {
30+
echo 'foo';
31+
}
32+
}
33+
34+
// Does not work
35+
class D extends A {
36+
use T;
37+
}
38+
echo "Done";
39+
40+
?>
41+
--EXPECT--
42+
Done
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
#[Override] attribute in trait does not check for parent class implementations (Variant with private parent method)
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
private function foo(): void {}
8+
}
9+
10+
trait T {
11+
#[\Override]
12+
public function foo(): void {
13+
echo 'foo';
14+
}
15+
}
16+
17+
class D extends A {
18+
use T;
19+
}
20+
echo "Done";
21+
22+
?>
23+
--EXPECTF--
24+
Fatal error: D::foo() has #[\Override] attribute, but no matching parent method exists in %s on line %d
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
#[Override] attribute in trait does not check for parent class implementations (Variant with protected parent method)
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
protected function foo(): void {}
8+
}
9+
10+
trait T {
11+
#[\Override]
12+
public function foo(): void {
13+
echo 'foo';
14+
}
15+
}
16+
17+
class D extends A {
18+
use T;
19+
}
20+
echo "Done";
21+
22+
?>
23+
--EXPECTF--
24+
Done
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
#[Override] attribute in trait does not check for parent class implementations (Variant with __construct)
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
public function __construct() {}
8+
}
9+
10+
trait T {
11+
#[\Override]
12+
public function __construct() {
13+
echo 'foo';
14+
}
15+
}
16+
17+
class D extends A {
18+
use T;
19+
}
20+
echo "Done";
21+
22+
?>
23+
--EXPECTF--
24+
Fatal error: D::__construct() has #[\Override] attribute, but no matching parent method exists in %s on line %d
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
#[Override] attribute in trait does not check for parent class implementations (Variant with abstract __construct)
3+
--FILE--
4+
<?php
5+
6+
abstract class A {
7+
abstract public function __construct();
8+
}
9+
10+
trait T {
11+
#[\Override]
12+
public function __construct() {
13+
echo 'foo';
14+
}
15+
}
16+
17+
class D extends A {
18+
use T;
19+
}
20+
echo "Done";
21+
22+
?>
23+
--EXPECT--
24+
Done
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
#[Override]: Inheritance check of inherited trait method against interface
3+
--FILE--
4+
<?php
5+
6+
trait T1 {
7+
public abstract function test();
8+
}
9+
10+
trait T2 {
11+
public function test() {}
12+
}
13+
14+
class A {
15+
use T2;
16+
}
17+
18+
class B extends A {
19+
use T1;
20+
}
21+
22+
?>
23+
===DONE===
24+
--EXPECT--
25+
===DONE===

Zend/zend_inheritance.c

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,12 +1081,13 @@ static void perform_delayable_implementation_check(
10811081
/**
10821082
* @param check_only Set to false to throw compile errors on incompatible methods, or true to return INHERITANCE_ERROR.
10831083
* @param checked Whether the compatibility check has already succeeded in zend_can_early_bind().
1084+
* @param force_mutable Whether we know that child may be modified, i.e. doesn't live in shm.
10841085
*/
10851086
static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(
10861087
zend_function *child, zend_class_entry *child_scope,
10871088
zend_function *parent, zend_class_entry *parent_scope,
10881089
zend_class_entry *ce, zval *child_zv,
1089-
bool check_visibility, bool check_only, bool checked) /* {{{ */
1090+
bool check_visibility, bool check_only, bool checked, bool force_mutable) /* {{{ */
10901091
{
10911092
uint32_t child_flags;
10921093
uint32_t parent_flags = parent->common.fn_flags;
@@ -1188,7 +1189,7 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(
11881189
perform_delayable_implementation_check(ce, child, child_scope, parent, parent_scope);
11891190
}
11901191

1191-
if (!check_only && child->common.scope == ce) {
1192+
if (!check_only && (child->common.scope == ce || force_mutable)) {
11921193
child->common.fn_flags &= ~ZEND_ACC_OVERRIDE;
11931194
}
11941195

@@ -1201,7 +1202,7 @@ static zend_never_inline void do_inheritance_check_on_method(
12011202
zend_function *parent, zend_class_entry *parent_scope,
12021203
zend_class_entry *ce, zval *child_zv, bool check_visibility)
12031204
{
1204-
do_inheritance_check_on_method_ex(child, child_scope, parent, parent_scope, ce, child_zv, check_visibility, 0, 0);
1205+
do_inheritance_check_on_method_ex(child, child_scope, parent, parent_scope, ce, child_zv, check_visibility, 0, 0, /* force_mutable */ false);
12051206
}
12061207

12071208
static zend_always_inline void do_inherit_method(zend_string *key, zend_function *parent, zend_class_entry *ce, bool is_interface, bool checked) /* {{{ */
@@ -1219,7 +1220,7 @@ static zend_always_inline void do_inherit_method(zend_string *key, zend_function
12191220
if (checked) {
12201221
do_inheritance_check_on_method_ex(
12211222
func, func->common.scope, parent, parent->common.scope, ce, child,
1222-
/* check_visibility */ 1, 0, checked);
1223+
/* check_visibility */ 1, 0, checked, /* force_mutable */ false);
12231224
} else {
12241225
do_inheritance_check_on_method(
12251226
func, func->common.scope, parent, parent->common.scope, ce, child,
@@ -1946,6 +1947,7 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
19461947
{
19471948
zend_function *existing_fn = NULL;
19481949
zend_function *new_fn;
1950+
bool check_inheritance = false;
19491951

19501952
if ((existing_fn = zend_hash_find_ptr(&ce->function_table, key)) != NULL) {
19511953
/* if it is the same function with the same visibility and has not been assigned a class scope yet, regardless
@@ -1980,11 +1982,7 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
19801982
ZSTR_VAL(ce->name), ZSTR_VAL(name),
19811983
ZSTR_VAL(existing_fn->common.scope->name), ZSTR_VAL(existing_fn->common.function_name));
19821984
} else {
1983-
/* Inherited members are overridden by members inserted by traits.
1984-
* Check whether the trait method fulfills the inheritance requirements. */
1985-
do_inheritance_check_on_method(
1986-
fn, fixup_trait_scope(fn, ce), existing_fn, fixup_trait_scope(existing_fn, ce),
1987-
ce, NULL, /* check_visibility */ 1);
1985+
check_inheritance = true;
19881986
}
19891987
}
19901988

@@ -2004,6 +2002,14 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
20042002
function_add_ref(new_fn);
20052003
fn = zend_hash_update_ptr(&ce->function_table, key, new_fn);
20062004
zend_add_magic_method(ce, fn, key);
2005+
2006+
if (check_inheritance) {
2007+
/* Inherited members are overridden by members inserted by traits.
2008+
* Check whether the trait method fulfills the inheritance requirements. */
2009+
do_inheritance_check_on_method_ex(
2010+
fn, fixup_trait_scope(fn, ce), existing_fn, fixup_trait_scope(existing_fn, ce),
2011+
ce, NULL, /* check_visibility */ 1, false, false, /* force_mutable */ true);
2012+
}
20072013
}
20082014
/* }}} */
20092015

@@ -3239,7 +3245,7 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e
32393245
do_inheritance_check_on_method_ex(
32403246
child_func, child_func->common.scope,
32413247
parent_func, parent_func->common.scope,
3242-
ce, NULL, /* check_visibility */ 1, 1, 0);
3248+
ce, NULL, /* check_visibility */ 1, 1, 0, /* force_mutable */ false);
32433249
if (UNEXPECTED(status == INHERITANCE_WARNING)) {
32443250
overall_status = INHERITANCE_WARNING;
32453251
} else if (UNEXPECTED(status != INHERITANCE_SUCCESS)) {

0 commit comments

Comments
 (0)