1
0
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:
rekmixa
2025-03-05 11:09:04 +01:00
committed by Ilija Tovilo
parent cab120f521
commit 4f5136cf2e
11 changed files with 324 additions and 0 deletions

View File

@@ -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.

View File

@@ -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) {
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)