Skip to content

Commit 4f5136c

Browse files
rekmixailuuu1994
authored andcommitted
Allow substituting static for self in final classes
Fixes phpGH-17725 Closes phpGH-17724
1 parent cab120f commit 4f5136c

11 files changed

+324
-0
lines changed

UPGRADING

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ PHP 8.5 UPGRADE NOTES
3636
the behavior of (bool) $object.
3737
. The return value of gc_collect_cycles() no longer includes strings and
3838
resources that were indirectly collected through cycles.
39+
. It is now allowed to substitute static with self or the concrete class name
40+
in final subclasses.
3941

4042
- Intl:
4143
. The extension now requires at least ICU 57.1.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
--TEST--
2+
Overriding static return types with self in final class
3+
--FILE--
4+
<?php
5+
6+
interface A
7+
{
8+
public function method1(): static;
9+
}
10+
11+
abstract class B
12+
{
13+
abstract public function method2(): static;
14+
}
15+
16+
trait C
17+
{
18+
abstract public function method3(): static;
19+
}
20+
21+
final class Foo extends B implements A
22+
{
23+
use C;
24+
25+
public function method1(): self
26+
{
27+
return $this;
28+
}
29+
30+
public function method2(): self
31+
{
32+
return $this;
33+
}
34+
35+
public function method3(): self
36+
{
37+
return $this;
38+
}
39+
}
40+
41+
final class Bar extends B implements A
42+
{
43+
use C;
44+
45+
public function method1(): Bar
46+
{
47+
return $this;
48+
}
49+
50+
public function method2(): Bar
51+
{
52+
return $this;
53+
}
54+
55+
public function method3(): Bar
56+
{
57+
return $this;
58+
}
59+
}
60+
61+
$foo = new Foo();
62+
63+
var_dump($foo->method1());
64+
var_dump($foo->method2());
65+
var_dump($foo->method3());
66+
67+
$bar = new Bar();
68+
69+
var_dump($bar->method1());
70+
var_dump($bar->method2());
71+
var_dump($bar->method3());
72+
?>
73+
--EXPECTF--
74+
object(Foo)#1 (0) {
75+
}
76+
object(Foo)#1 (0) {
77+
}
78+
object(Foo)#1 (0) {
79+
}
80+
object(Bar)#2 (0) {
81+
}
82+
object(Bar)#2 (0) {
83+
}
84+
object(Bar)#2 (0) {
85+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Overriding static return types with self in non-final class
3+
--FILE--
4+
<?php
5+
6+
interface A
7+
{
8+
public function method1(): static;
9+
}
10+
11+
class Foo implements A
12+
{
13+
public function method1(): self
14+
{
15+
return $this;
16+
}
17+
}
18+
19+
$foo = new Foo();
20+
21+
var_dump($foo->method1());
22+
?>
23+
--EXPECTF--
24+
Fatal error: Declaration of Foo::method1(): Foo must be compatible with A::method1(): static in %s on line %d
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
--TEST--
2+
Overriding static return types with self in final class with union types
3+
--FILE--
4+
<?php
5+
6+
interface A
7+
{
8+
public function methodScalar(): static|string;
9+
public function methodIterable1(): static|iterable;
10+
public function methodIterable2(): static|array;
11+
public function methodObject1(): static|A;
12+
public function methodObject2(): static|B;
13+
public function methodObject3(): static|C;
14+
public function methodObject4(): static|self;
15+
public function methodNullable1(): ?static;
16+
public function methodNullable2(): static|null;
17+
}
18+
19+
final class B implements A
20+
{
21+
public function methodScalar(): self { return $this; }
22+
public function methodIterable1(): self|iterable { return $this; }
23+
public function methodIterable2(): array { return []; }
24+
public function methodObject1(): self { return $this; }
25+
public function methodObject2(): B { return $this; }
26+
public function methodObject3(): C { return new C(); }
27+
public function methodObject4(): self { return $this; }
28+
public function methodNullable1(): ?static { return $this; }
29+
public function methodNullable2(): ?static { return null; }
30+
}
31+
32+
class C
33+
{
34+
}
35+
36+
$b = new B();
37+
var_dump($b->methodScalar());
38+
var_dump($b->methodIterable1());
39+
var_dump($b->methodIterable2());
40+
var_dump($b->methodObject1());
41+
var_dump($b->methodObject2());
42+
var_dump($b->methodObject3());
43+
var_dump($b->methodObject4());
44+
var_dump($b->methodNullable1());
45+
var_dump($b->methodNullable2());
46+
?>
47+
--EXPECTF--
48+
object(B)#1 (0) {
49+
}
50+
object(B)#1 (0) {
51+
}
52+
array(0) {
53+
}
54+
object(B)#1 (0) {
55+
}
56+
object(B)#1 (0) {
57+
}
58+
object(C)#2 (0) {
59+
}
60+
object(B)#1 (0) {
61+
}
62+
object(B)#1 (0) {
63+
}
64+
NULL
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
Overriding return type with type that is not in the interface in final class with union types
3+
--FILE--
4+
<?php
5+
6+
interface A
7+
{
8+
public function methodScalar1(): static|bool;
9+
}
10+
11+
final class B implements A
12+
{
13+
public function methodScalar1(): array { return []; }
14+
}
15+
16+
$b = new B();
17+
var_dump($b->methodScalar1());
18+
?>
19+
--EXPECTF--
20+
Fatal error: Declaration of B::methodScalar1(): array must be compatible with A::methodScalar1(): static|bool 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+
Overriding static with self and add a type that is not in the interface in final class
3+
--FILE--
4+
<?php
5+
6+
interface A
7+
{
8+
public function methodScalar1(): static|bool;
9+
}
10+
11+
final class B implements A
12+
{
13+
public function methodScalar1(): self|array { return []; }
14+
}
15+
16+
$b = new B();
17+
var_dump($b->methodScalar1());
18+
?>
19+
--EXPECTF--
20+
Fatal error: Declaration of B::methodScalar1(): B|array must be compatible with A::methodScalar1(): static|bool in %s on line %d
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Override static with another implementation of interface in final class
3+
--FILE--
4+
<?php
5+
6+
interface A
7+
{
8+
public function methodScalar1(): static|bool;
9+
}
10+
11+
final class C implements A
12+
{
13+
public function methodScalar1(): self { return $this; }
14+
}
15+
16+
final class B implements A
17+
{
18+
public function methodScalar1(): C { return new C(); }
19+
}
20+
21+
$b = new B();
22+
var_dump($b->methodScalar1());
23+
?>
24+
--EXPECTF--
25+
Fatal error: Declaration of B::methodScalar1(): C must be compatible with A::methodScalar1(): static|bool in %s on line %d
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Override static with another implementation of interface and add a type that is not in the interface in final class
3+
--FILE--
4+
<?php
5+
6+
interface A
7+
{
8+
public function methodScalar1(): static|bool;
9+
}
10+
11+
final class C implements A
12+
{
13+
public function methodScalar1(): self { return $this; }
14+
}
15+
16+
final class B implements A
17+
{
18+
public function methodScalar1(): C|array { return []; }
19+
}
20+
21+
$b = new B();
22+
var_dump($b->methodScalar1());
23+
?>
24+
--EXPECTF--
25+
Fatal error: Declaration of B::methodScalar1(): C|array must be compatible with A::methodScalar1(): static|bool 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 static with class that is even not an implementation of interface in final class
3+
--FILE--
4+
<?php
5+
6+
interface A
7+
{
8+
public function methodScalar1(): static|bool;
9+
}
10+
11+
final class C
12+
{
13+
}
14+
15+
final class B implements A
16+
{
17+
public function methodScalar1(): C { return new C(); }
18+
}
19+
20+
$b = new B();
21+
var_dump($b->methodScalar1());
22+
?>
23+
--EXPECTF--
24+
Fatal error: Declaration of B::methodScalar1(): C must be compatible with A::methodScalar1(): static|bool 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 static with class that is even not an implementation of interface and add a type that is not in the interface in final class
3+
--FILE--
4+
<?php
5+
6+
interface A
7+
{
8+
public function methodScalar1(): static|bool;
9+
}
10+
11+
final class C
12+
{
13+
}
14+
15+
final class B implements A
16+
{
17+
public function methodScalar1(): C|array { return []; }
18+
}
19+
20+
$b = new B();
21+
var_dump($b->methodScalar1());
22+
?>
23+
--EXPECTF--
24+
Fatal error: Declaration of B::methodScalar1(): C|array must be compatible with A::methodScalar1(): static|bool in %s on line %d

Zend/zend_inheritance.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,17 @@ static inheritance_status zend_is_class_subtype_of_type(
502502
}
503503
}
504504

505+
/* If the parent has 'static' as a return type, then final classes could replace it with self */
506+
if ((ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_STATIC) && (fe_scope->ce_flags & ZEND_ACC_FINAL)) {
507+
if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name);
508+
if (!fe_ce) {
509+
have_unresolved = 1;
510+
} else if (fe_ce == fe_scope) {
511+
track_class_dependency(fe_ce, fe_class_name);
512+
return INHERITANCE_SUCCESS;
513+
}
514+
}
515+
505516
zend_type *single_type;
506517

507518
/* Traverse the list of parent types and check if the current child (FE)

0 commit comments

Comments
 (0)