mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
Allow substituting static for self in final classes
Fixes GH-17725 Closes GH-17724
This commit is contained in:
@@ -36,6 +36,8 @@ PHP 8.5 UPGRADE NOTES
|
||||
the behavior of (bool) $object.
|
||||
. The return value of gc_collect_cycles() no longer includes strings and
|
||||
resources that were indirectly collected through cycles.
|
||||
. It is now allowed to substitute static with self or the concrete class name
|
||||
in final subclasses.
|
||||
|
||||
- Intl:
|
||||
. The extension now requires at least ICU 57.1.
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
--TEST--
|
||||
Overriding static return types with self in final class
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A
|
||||
{
|
||||
public function method1(): static;
|
||||
}
|
||||
|
||||
abstract class B
|
||||
{
|
||||
abstract public function method2(): static;
|
||||
}
|
||||
|
||||
trait C
|
||||
{
|
||||
abstract public function method3(): static;
|
||||
}
|
||||
|
||||
final class Foo extends B implements A
|
||||
{
|
||||
use C;
|
||||
|
||||
public function method1(): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function method2(): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function method3(): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
final class Bar extends B implements A
|
||||
{
|
||||
use C;
|
||||
|
||||
public function method1(): Bar
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function method2(): Bar
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function method3(): Bar
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
$foo = new Foo();
|
||||
|
||||
var_dump($foo->method1());
|
||||
var_dump($foo->method2());
|
||||
var_dump($foo->method3());
|
||||
|
||||
$bar = new Bar();
|
||||
|
||||
var_dump($bar->method1());
|
||||
var_dump($bar->method2());
|
||||
var_dump($bar->method3());
|
||||
?>
|
||||
--EXPECTF--
|
||||
object(Foo)#1 (0) {
|
||||
}
|
||||
object(Foo)#1 (0) {
|
||||
}
|
||||
object(Foo)#1 (0) {
|
||||
}
|
||||
object(Bar)#2 (0) {
|
||||
}
|
||||
object(Bar)#2 (0) {
|
||||
}
|
||||
object(Bar)#2 (0) {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
--TEST--
|
||||
Overriding static return types with self in non-final class
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A
|
||||
{
|
||||
public function method1(): static;
|
||||
}
|
||||
|
||||
class Foo implements A
|
||||
{
|
||||
public function method1(): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
$foo = new Foo();
|
||||
|
||||
var_dump($foo->method1());
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of Foo::method1(): Foo must be compatible with A::method1(): static in %s on line %d
|
||||
@@ -0,0 +1,64 @@
|
||||
--TEST--
|
||||
Overriding static return types with self in final class with union types
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A
|
||||
{
|
||||
public function methodScalar(): static|string;
|
||||
public function methodIterable1(): static|iterable;
|
||||
public function methodIterable2(): static|array;
|
||||
public function methodObject1(): static|A;
|
||||
public function methodObject2(): static|B;
|
||||
public function methodObject3(): static|C;
|
||||
public function methodObject4(): static|self;
|
||||
public function methodNullable1(): ?static;
|
||||
public function methodNullable2(): static|null;
|
||||
}
|
||||
|
||||
final class B implements A
|
||||
{
|
||||
public function methodScalar(): self { return $this; }
|
||||
public function methodIterable1(): self|iterable { return $this; }
|
||||
public function methodIterable2(): array { return []; }
|
||||
public function methodObject1(): self { return $this; }
|
||||
public function methodObject2(): B { return $this; }
|
||||
public function methodObject3(): C { return new C(); }
|
||||
public function methodObject4(): self { return $this; }
|
||||
public function methodNullable1(): ?static { return $this; }
|
||||
public function methodNullable2(): ?static { return null; }
|
||||
}
|
||||
|
||||
class C
|
||||
{
|
||||
}
|
||||
|
||||
$b = new B();
|
||||
var_dump($b->methodScalar());
|
||||
var_dump($b->methodIterable1());
|
||||
var_dump($b->methodIterable2());
|
||||
var_dump($b->methodObject1());
|
||||
var_dump($b->methodObject2());
|
||||
var_dump($b->methodObject3());
|
||||
var_dump($b->methodObject4());
|
||||
var_dump($b->methodNullable1());
|
||||
var_dump($b->methodNullable2());
|
||||
?>
|
||||
--EXPECTF--
|
||||
object(B)#1 (0) {
|
||||
}
|
||||
object(B)#1 (0) {
|
||||
}
|
||||
array(0) {
|
||||
}
|
||||
object(B)#1 (0) {
|
||||
}
|
||||
object(B)#1 (0) {
|
||||
}
|
||||
object(C)#2 (0) {
|
||||
}
|
||||
object(B)#1 (0) {
|
||||
}
|
||||
object(B)#1 (0) {
|
||||
}
|
||||
NULL
|
||||
@@ -0,0 +1,20 @@
|
||||
--TEST--
|
||||
Overriding return type with type that is not in the interface in final class with union types
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A
|
||||
{
|
||||
public function methodScalar1(): static|bool;
|
||||
}
|
||||
|
||||
final class B implements A
|
||||
{
|
||||
public function methodScalar1(): array { return []; }
|
||||
}
|
||||
|
||||
$b = new B();
|
||||
var_dump($b->methodScalar1());
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of B::methodScalar1(): array must be compatible with A::methodScalar1(): static|bool in %s on line %d
|
||||
@@ -0,0 +1,20 @@
|
||||
--TEST--
|
||||
Overriding static with self and add a type that is not in the interface in final class
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A
|
||||
{
|
||||
public function methodScalar1(): static|bool;
|
||||
}
|
||||
|
||||
final class B implements A
|
||||
{
|
||||
public function methodScalar1(): self|array { return []; }
|
||||
}
|
||||
|
||||
$b = new B();
|
||||
var_dump($b->methodScalar1());
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of B::methodScalar1(): B|array must be compatible with A::methodScalar1(): static|bool in %s on line %d
|
||||
@@ -0,0 +1,25 @@
|
||||
--TEST--
|
||||
Override static with another implementation of interface in final class
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A
|
||||
{
|
||||
public function methodScalar1(): static|bool;
|
||||
}
|
||||
|
||||
final class C implements A
|
||||
{
|
||||
public function methodScalar1(): self { return $this; }
|
||||
}
|
||||
|
||||
final class B implements A
|
||||
{
|
||||
public function methodScalar1(): C { return new C(); }
|
||||
}
|
||||
|
||||
$b = new B();
|
||||
var_dump($b->methodScalar1());
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of B::methodScalar1(): C must be compatible with A::methodScalar1(): static|bool in %s on line %d
|
||||
@@ -0,0 +1,25 @@
|
||||
--TEST--
|
||||
Override static with another implementation of interface and add a type that is not in the interface in final class
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A
|
||||
{
|
||||
public function methodScalar1(): static|bool;
|
||||
}
|
||||
|
||||
final class C implements A
|
||||
{
|
||||
public function methodScalar1(): self { return $this; }
|
||||
}
|
||||
|
||||
final class B implements A
|
||||
{
|
||||
public function methodScalar1(): C|array { return []; }
|
||||
}
|
||||
|
||||
$b = new B();
|
||||
var_dump($b->methodScalar1());
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of B::methodScalar1(): C|array must be compatible with A::methodScalar1(): static|bool in %s on line %d
|
||||
@@ -0,0 +1,24 @@
|
||||
--TEST--
|
||||
Override static with class that is even not an implementation of interface in final class
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A
|
||||
{
|
||||
public function methodScalar1(): static|bool;
|
||||
}
|
||||
|
||||
final class C
|
||||
{
|
||||
}
|
||||
|
||||
final class B implements A
|
||||
{
|
||||
public function methodScalar1(): C { return new C(); }
|
||||
}
|
||||
|
||||
$b = new B();
|
||||
var_dump($b->methodScalar1());
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of B::methodScalar1(): C must be compatible with A::methodScalar1(): static|bool in %s on line %d
|
||||
@@ -0,0 +1,24 @@
|
||||
--TEST--
|
||||
Override static with class that is even not an implementation of interface and add a type that is not in the interface in final class
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A
|
||||
{
|
||||
public function methodScalar1(): static|bool;
|
||||
}
|
||||
|
||||
final class C
|
||||
{
|
||||
}
|
||||
|
||||
final class B implements A
|
||||
{
|
||||
public function methodScalar1(): C|array { return []; }
|
||||
}
|
||||
|
||||
$b = new B();
|
||||
var_dump($b->methodScalar1());
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of B::methodScalar1(): C|array must be compatible with A::methodScalar1(): static|bool in %s on line %d
|
||||
@@ -502,6 +502,17 @@ static inheritance_status zend_is_class_subtype_of_type(
|
||||
}
|
||||
}
|
||||
|
||||
/* If the parent has 'static' as a return type, then final classes could replace it with self */
|
||||
if ((ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_STATIC) && (fe_scope->ce_flags & ZEND_ACC_FINAL)) {
|
||||
if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name);
|
||||
if (!fe_ce) {
|
||||
have_unresolved = 1;
|
||||
} else if (fe_ce == fe_scope) {
|
||||
track_class_dependency(fe_ce, fe_class_name);
|
||||
return INHERITANCE_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
zend_type *single_type;
|
||||
|
||||
/* Traverse the list of parent types and check if the current child (FE)
|
||||
|
||||
Reference in New Issue
Block a user