Skip to content

Commit f74e30c

Browse files
committed
Check abstract method signatures coming from traits
RFC: https://wiki.php.net/rfc/abstract_trait_method_validation Closes GH-5068.
1 parent beaacbc commit f74e30c

12 files changed

+218
-76
lines changed

UPGRADING

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,21 @@ PHP 8.0 UPGRADE NOTES
163163
accepted, and func as assumed to refer to T1::func. Now it will generate a
164164
fatal error instead, and either T1::func or T2::func needs to be written
165165
explicitly.
166+
. The signature of abstract methods defined in traits is now checked against
167+
the implementing class method:
168+
169+
trait MyTrait {
170+
abstract private function neededByTrait(): string;
171+
}
172+
173+
class MyClass {
174+
use MyTrait;
175+
176+
// Error, because of return type mismatch.
177+
private function neededByTrait(): int { return 42; }
178+
}
179+
180+
RFC: https://wiki.php.net/rfc/abstract_trait_method_validation
166181

167182
- COM:
168183
. Removed the ability to import case-insensitive constants from type
@@ -428,8 +443,10 @@ PHP 8.0 UPGRADE NOTES
428443
. Some consistency fixes to variable syntax have been applied, for example
429444
writing `Foo::BAR::$baz` is now allowed.
430445
RFC: https://wiki.php.net/rfc/variable_syntax_tweaks
431-
. Added Stringable.
446+
. Added Stringable interface, which is automatically implemented if a class
447+
defines a __toString() method.
432448
RFC: https://wiki.php.net/rfc/stringable
449+
. Traits can now define abstract private methods.
433450

434451
- Date:
435452
. Added DateTime::createFromInterface() and
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Abstract method from trait enforced in class
3+
--FILE--
4+
<?php
5+
6+
trait T {
7+
abstract public function neededByTheTrait(int $a, string $b);
8+
}
9+
10+
class C {
11+
use T;
12+
13+
public function neededByTheTrait(array $a, object $b) {}
14+
}
15+
16+
?>
17+
--EXPECTF--
18+
Fatal error: Declaration of C::neededByTheTrait(array $a, object $b) must be compatible with T::neededByTheTrait(int $a, string $b) in %s on line %d
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
Mutually incompatible methods from traits are fine as long as the final method is compatible
3+
--FILE--
4+
<?php
5+
6+
trait T1 {
7+
abstract public function test();
8+
}
9+
trait T2 {
10+
abstract public function test(): int;
11+
}
12+
13+
class C {
14+
use T1, T2;
15+
16+
public function test(): int {}
17+
}
18+
19+
?>
20+
===DONE===
21+
--EXPECT--
22+
===DONE===
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Private abstract method from trait enforced in class
3+
--FILE--
4+
<?php
5+
6+
trait T {
7+
abstract private function neededByTheTrait(int $a, string $b);
8+
}
9+
10+
class C {
11+
use T;
12+
13+
private function neededByTheTrait(array $a, object $b) {}
14+
}
15+
16+
?>
17+
--EXPECTF--
18+
Fatal error: Declaration of C::neededByTheTrait(array $a, object $b) must be compatible with T::neededByTheTrait(int $a, string $b) in %s on line %d
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
Visibility enforcement on abstract trait methods
3+
--FILE--
4+
<?php
5+
6+
trait T {
7+
abstract public function method(int $a, string $b);
8+
}
9+
10+
class C {
11+
use T;
12+
13+
/* For backwards-compatibility reasons, visibility is not enforced here. */
14+
private function method(int $a, string $b) {}
15+
}
16+
17+
?>
18+
===DONE===
19+
--EXPECT--
20+
===DONE===
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Staticness enforcement on abstract trait methods
3+
--FILE--
4+
<?php
5+
6+
trait T {
7+
abstract static public function method(int $a, string $b);
8+
}
9+
10+
class C {
11+
use T;
12+
13+
public function method(int $a, string $b) {}
14+
}
15+
16+
?>
17+
--EXPECTF--
18+
Fatal error: Cannot make static method T::method() non static in class C in %s on line %d
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
Abstract private trait method not implemented
3+
--FILE--
4+
<?php
5+
6+
trait T {
7+
abstract private function method(int $a, string $b);
8+
}
9+
10+
abstract class C {
11+
use T;
12+
}
13+
14+
class D extends C {
15+
private function method(int $a, string $b) {}
16+
}
17+
18+
?>
19+
--EXPECTF--
20+
Fatal error: Class C must implement 1 abstract private method (C::method) in %s on line %d
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Abstract private trait method forwarded as abstract protected method
3+
--FILE--
4+
<?php
5+
6+
trait T {
7+
abstract private function method(int $a, string $b);
8+
}
9+
10+
abstract class C {
11+
use T;
12+
13+
abstract protected function method(int $a, string $b);
14+
}
15+
16+
class D extends C {
17+
protected function method(int $a, string $b) {}
18+
}
19+
20+
?>
21+
===DONE===
22+
--EXPECT--
23+
===DONE===

Zend/tests/traits/bug60217b.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ class CBroken {
2222
$o = new CBroken;
2323
$o->foo(1);
2424
--EXPECTF--
25-
Fatal error: Declaration of TBroken1::foo($a) must be compatible with TBroken2::foo($a, $b = 0) in %s
25+
Fatal error: Declaration of CBroken::foo($a) must be compatible with TBroken2::foo($a, $b = 0) in %s on line %d

Zend/tests/traits/bug60217c.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ class CBroken {
2222
$o = new CBroken;
2323
$o->foo(1);
2424
--EXPECTF--
25-
Fatal error: Declaration of TBroken2::foo($a) must be compatible with TBroken1::foo($a, $b = 0) in %s on line %d
25+
Fatal error: Declaration of CBroken::foo($a) must be compatible with TBroken1::foo($a, $b = 0) in %s on line %d

Zend/zend_compile.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6111,7 +6111,7 @@ void zend_begin_method_decl(zend_op_array *op_array, zend_string *name, zend_boo
61116111
}
61126112

61136113
if (op_array->fn_flags & ZEND_ACC_ABSTRACT) {
6114-
if (op_array->fn_flags & ZEND_ACC_PRIVATE) {
6114+
if ((op_array->fn_flags & ZEND_ACC_PRIVATE) && !(ce->ce_flags & ZEND_ACC_TRAIT)) {
61156115
zend_error_noreturn(E_COMPILE_ERROR, "%s function %s::%s() cannot be declared private",
61166116
in_interface ? "Interface" : "Abstract", ZSTR_VAL(ce->name), ZSTR_VAL(name));
61176117
}

0 commit comments

Comments
 (0)