Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Override static in return types with self in final classes #17724

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
79e4f25
add override_static_type_with_self_in_final_class.phpt
rekmixa Feb 6, 2025
4ec78c1
allow to override static return types with self in final classes
rekmixa Feb 6, 2025
27f8e02
fix checking covariant on override static with self & add tests for o…
rekmixa Feb 6, 2025
218d03d
change test override_static_type_with_self_in_final_class.phpt
rekmixa Feb 6, 2025
f2a4a2d
fix tests for overriding static with self in non-final classes
rekmixa Feb 6, 2025
ceb8396
fix tests for overriding static with self in non-final classes
rekmixa Feb 6, 2025
7fe477d
#17725 add more tests
Feb 7, 2025
6b3ca17
#17725 change way to checking availability to replace static with sel…
rekmixa Feb 10, 2025
31c53e4
#17725 add more tests for union types
rekmixa Feb 10, 2025
baa7b49
#17725 fix checking inheritance while replacing static with self in f…
rekmixa Feb 10, 2025
1d353f8
#17725 fix checking inheritance while replacing static with self in f…
rekmixa Feb 10, 2025
9369f53
#17725 add additional check for mask MAY_BE_STATIC
rekmixa Feb 10, 2025
8b5f894
#17725 optimize getting proto_type full mask in zend_is_class_subtype…
rekmixa Feb 10, 2025
573b8de
#17725 add brackets
rekmixa Feb 11, 2025
2f4bbcb
#17725 remove unnecessary tests & change test descriptions & move tes…
rekmixa Feb 11, 2025
8740b58
#17725 change tests & change comparing instance of while checking abi…
Feb 13, 2025
25184ce
#17725 fix test override_static_type_with_self_in_final_class_with_un…
Feb 13, 2025
a77482d
#17725 rollback moving ZEND_TYPE_FULL_MASK to variable
Feb 19, 2025
a957dbf
#17725 remove redundand checking for is_intersection & move checking …
Feb 19, 2025
1b95868
#17725 change checking ability to replace static with self & update t…
rekmixa Feb 19, 2025
66df47f
#17725 remove unlinked_instanceof
rekmixa Feb 19, 2025
de34c3e
#17725 update comment
rekmixa Feb 19, 2025
1dc2def
#17725 remove redundant whitespace change
rekmixa Feb 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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) {
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
12 changes: 12 additions & 0 deletions Zend/zend_inheritance.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -570,6 +581,7 @@ static inheritance_status zend_is_class_subtype_of_type(
if (have_unresolved) {
return INHERITANCE_UNRESOLVED;
}

return is_intersection ? INHERITANCE_SUCCESS : INHERITANCE_ERROR;
}

Expand Down
Loading