Skip to content

Commit 4344385

Browse files
committed
Add static return type
RFC: https://wiki.php.net/rfc/static_return_type The "static" type is represented as MAY_BE_STATIC, rather than a class type like "self" and "parent", as it has special resolution semantics, and cannot be cached in the runtime cache. Closes GH-5062.
1 parent d2ba848 commit 4344385

22 files changed

+400
-26
lines changed

UPGRADING

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,15 @@ PHP 8.0 UPGRADE NOTES
367367
class B extends A {
368368
public function method(...$everything) {}
369369
}
370+
. "static" (as in "late static binding") can now be used as a return type:
371+
372+
class Test {
373+
public function create(): static {
374+
return new static();
375+
}
376+
}
377+
378+
RFC: https://wiki.php.net/rfc/static_return_type
370379
. It is now possible to fetch the class name of an object using
371380
`$object::class`. The result is the same as `get_class($object)`.
372381
RFC: https://wiki.php.net/rfc/class_name_literal_on_object
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
While it may not be very useful, static is also permitted in final classes
3+
--FILE--
4+
<?php
5+
6+
final class Test {
7+
public static function create(): static {
8+
return new static;
9+
}
10+
}
11+
12+
var_dump(Test::create());
13+
14+
?>
15+
--EXPECT--
16+
object(Test)#1 (0) {
17+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Static type outside class generates compile error
3+
--FILE--
4+
<?php
5+
6+
function test(): static {}
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: Cannot use "static" when no class scope is active in %s on line %d
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Static type is not allowed in parameters
3+
--FILE--
4+
<?php
5+
6+
// TODO: We could prohibit this case in the compiler instead.
7+
8+
class Test {
9+
public function test(static $param) {
10+
}
11+
}
12+
13+
?>
14+
--EXPECTF--
15+
Parse error: syntax error, unexpected 'static' (T_STATIC), expecting variable (T_VARIABLE) in %s on line %d
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
Static type is not allowed in properties
3+
--FILE--
4+
<?php
5+
6+
// Testing ?static here, to avoid ambiguity with static properties.
7+
class Test {
8+
public ?static $foo;
9+
}
10+
11+
?>
12+
--EXPECTF--
13+
Parse error: syntax error, unexpected 'static' (T_STATIC) in %s on line %d
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
--TEST--
2+
Static return type
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
public function test1(): static {
8+
return new static;
9+
}
10+
public function test2(): static {
11+
return new self;
12+
}
13+
public function test3(): static {
14+
return new static;
15+
}
16+
public function test4(): static|array {
17+
return new self;
18+
}
19+
}
20+
21+
class B extends A {
22+
public function test3(): static {
23+
return new C;
24+
}
25+
}
26+
27+
class C extends B {}
28+
29+
$a = new A;
30+
$b = new B;
31+
32+
var_dump($a->test1());
33+
var_dump($b->test1());
34+
35+
echo "\n";
36+
var_dump($a->test2());
37+
try {
38+
var_dump($b->test2());
39+
} catch (TypeError $e) {
40+
echo $e->getMessage(), "\n";
41+
}
42+
43+
echo "\n";
44+
var_dump($a->test3());
45+
var_dump($b->test3());
46+
47+
echo "\n";
48+
var_dump($a->test4());
49+
try {
50+
var_dump($b->test4());
51+
} catch (TypeError $e) {
52+
echo $e->getMessage(), "\n";
53+
}
54+
55+
echo "\n";
56+
$test = function($x): static {
57+
return $x;
58+
};
59+
60+
try {
61+
var_dump($test(new stdClass));
62+
} catch (TypeError $e) {
63+
echo $e->getMessage(), "\n";
64+
}
65+
66+
$test = $test->bindTo($a);
67+
var_dump($test($a));
68+
69+
?>
70+
--EXPECT--
71+
object(A)#3 (0) {
72+
}
73+
object(B)#3 (0) {
74+
}
75+
76+
object(A)#3 (0) {
77+
}
78+
Return value of A::test2() must be an instance of B, instance of A returned
79+
80+
object(A)#3 (0) {
81+
}
82+
object(C)#3 (0) {
83+
}
84+
85+
object(A)#3 (0) {
86+
}
87+
Return value of A::test4() must be of type B|array, instance of A returned
88+
89+
Return value of {closure}() must be an instance of static, instance of stdClass returned
90+
object(A)#1 (0) {
91+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
static type in trait
3+
--FILE--
4+
<?php
5+
6+
trait T {
7+
public function test($arg): static {
8+
return $arg;
9+
}
10+
}
11+
12+
class C {
13+
use T;
14+
}
15+
class P extends C {
16+
}
17+
18+
$c = new C;
19+
$p = new P;
20+
var_dump($c->test($c));
21+
var_dump($c->test($p));
22+
var_dump($p->test($p));
23+
var_dump($p->test($c));
24+
25+
?>
26+
--EXPECTF--
27+
object(C)#1 (0) {
28+
}
29+
object(P)#2 (0) {
30+
}
31+
object(P)#2 (0) {
32+
}
33+
34+
Fatal error: Uncaught TypeError: Return value of C::test() must be an instance of P, instance of C returned in %s:%d
35+
Stack trace:
36+
#0 %s(%d): C->test(Object(C))
37+
#1 {main}
38+
thrown in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
object and static are redundant
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public function foo(): static|object {}
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Type static|object contains both object and a class type, which is redundant in %s on line %d
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Failure case for static variance: Replace static with self
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
public function test(): static {}
8+
}
9+
class B extends A {
10+
public function test(): self {}
11+
}
12+
13+
?>
14+
--EXPECTF--
15+
Fatal error: Declaration of B::test(): B must be compatible with A::test(): static 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+
Success cases for static variance
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
public function test1(): self {}
8+
public function test2(): B {}
9+
public function test3(): object {}
10+
public function test4(): X|Y|self {}
11+
public function test5(): ?static {}
12+
}
13+
14+
class B extends A {
15+
public function test1(): static {}
16+
public function test2(): static {}
17+
public function test3(): static {}
18+
public function test4(): X|Y|static {}
19+
public function test5(): static {}
20+
}
21+
22+
?>
23+
===DONE===
24+
--EXPECT--
25+
===DONE===

Zend/zend_compile.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,16 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
11961196
}
11971197

11981198
uint32_t type_mask = ZEND_TYPE_FULL_MASK(type);
1199+
if (type_mask & MAY_BE_STATIC) {
1200+
zend_string *name = ZSTR_KNOWN(ZEND_STR_STATIC);
1201+
if (scope) {
1202+
zend_class_entry *called_scope = zend_get_called_scope(EG(current_execute_data));
1203+
if (called_scope) {
1204+
name = called_scope->name;
1205+
}
1206+
}
1207+
str = add_type_string(str, name);
1208+
}
11991209
if (type_mask & MAY_BE_CALLABLE) {
12001210
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE));
12011211
}
@@ -5502,6 +5512,10 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
55025512
{
55035513
ZEND_ASSERT(!(ast->attr & ZEND_TYPE_NULLABLE));
55045514
if (ast->kind == ZEND_AST_TYPE) {
5515+
if (ast->attr == IS_STATIC && !CG(active_class_entry) && zend_is_scope_known()) {
5516+
zend_error_noreturn(E_COMPILE_ERROR,
5517+
"Cannot use \"static\" when no class scope is active");
5518+
}
55055519
return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, 0, 0);
55065520
} else {
55075521
zend_string *class_name = zend_ast_get_str(ast);
@@ -5657,7 +5671,7 @@ static zend_type zend_compile_typename(
56575671
ZSTR_VAL(type_str));
56585672
}
56595673

5660-
if ((type_mask & MAY_BE_OBJECT) && ZEND_TYPE_HAS_CLASS(type)) {
5674+
if ((type_mask & MAY_BE_OBJECT) && (ZEND_TYPE_HAS_CLASS(type) || (type_mask & MAY_BE_STATIC))) {
56615675
zend_string *type_str = zend_type_to_string(type);
56625676
zend_error_noreturn(E_COMPILE_ERROR,
56635677
"Type %s contains both object and a class type, which is redundant",

Zend/zend_execute.c

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@ static ZEND_COLD void zend_verify_type_error_common(
674674
}
675675

676676
if (is_union_type(arg_info->type)) {
677-
zend_string *type_str = zend_type_to_string(arg_info->type);
677+
zend_string *type_str = zend_type_to_string_resolved(arg_info->type, zf->common.scope);
678678
smart_str_appends(&str, "be of type ");
679679
smart_str_append(&str, type_str);
680680
zend_string_release(type_str);
@@ -714,6 +714,16 @@ static ZEND_COLD void zend_verify_type_error_common(
714714
case MAY_BE_ITERABLE:
715715
smart_str_appends(&str, "be iterable");
716716
break;
717+
case MAY_BE_STATIC: {
718+
zend_class_entry *called_scope = zend_get_called_scope(EG(current_execute_data));
719+
smart_str_appends(&str, "be an instance of ");
720+
if (called_scope) {
721+
smart_str_append(&str, called_scope->name);
722+
} else {
723+
smart_str_appends(&str, "static");
724+
}
725+
break;
726+
}
717727
default:
718728
{
719729
/* Hack to print the type without null */
@@ -735,7 +745,9 @@ static ZEND_COLD void zend_verify_type_error_common(
735745
*need_msg = smart_str_extract(&str);
736746

737747
if (value) {
738-
if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(value) == IS_OBJECT) {
748+
zend_bool has_class = ZEND_TYPE_HAS_CLASS(arg_info->type)
749+
|| (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_STATIC);
750+
if (has_class && Z_TYPE_P(value) == IS_OBJECT) {
739751
*given_msg = "instance of ";
740752
*given_kind = ZSTR_VAL(Z_OBJCE_P(value)->name);
741753
} else {
@@ -988,11 +1000,12 @@ static zend_always_inline zend_bool i_zend_check_property_type(zend_property_inf
9881000
return 1;
9891001
}
9901002

991-
ZEND_ASSERT(!(ZEND_TYPE_FULL_MASK(info->type) & MAY_BE_CALLABLE));
992-
if ((ZEND_TYPE_FULL_MASK(info->type) & MAY_BE_ITERABLE) && zend_is_iterable(property)) {
1003+
uint32_t type_mask = ZEND_TYPE_FULL_MASK(info->type);
1004+
ZEND_ASSERT(!(type_mask & (MAY_BE_CALLABLE|MAY_BE_STATIC)));
1005+
if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(property)) {
9931006
return 1;
9941007
}
995-
return zend_verify_scalar_type_hint(ZEND_TYPE_FULL_MASK(info->type), property, strict, 0);
1008+
return zend_verify_scalar_type_hint(type_mask, property, strict, 0);
9961009
}
9971010

9981011
static zend_always_inline zend_bool i_zend_verify_property_type(zend_property_info *info, zval *property, zend_bool strict)
@@ -1024,6 +1037,19 @@ static zend_never_inline zval* zend_assign_to_typed_prop(zend_property_info *inf
10241037
return zend_assign_to_variable(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES());
10251038
}
10261039

1040+
ZEND_API zend_bool zend_value_instanceof_static(zval *zv) {
1041+
if (Z_TYPE_P(zv) != IS_OBJECT) {
1042+
return 0;
1043+
}
1044+
1045+
zend_class_entry *called_scope = zend_get_called_scope(EG(current_execute_data));
1046+
if (!called_scope) {
1047+
return 0;
1048+
}
1049+
return instanceof_function(Z_OBJCE_P(zv), called_scope);
1050+
}
1051+
1052+
10271053
static zend_always_inline zend_bool zend_check_type_slow(
10281054
zend_type type, zval *arg, zend_reference *ref, void **cache_slot, zend_class_entry *scope,
10291055
zend_bool is_return_type, zend_bool is_internal)
@@ -1074,6 +1100,9 @@ static zend_always_inline zend_bool zend_check_type_slow(
10741100
if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) {
10751101
return 1;
10761102
}
1103+
if ((type_mask & MAY_BE_STATIC) && zend_value_instanceof_static(arg)) {
1104+
return 1;
1105+
}
10771106
if (ref && ZEND_REF_HAS_TYPE_SOURCES(ref)) {
10781107
/* We cannot have conversions for typed refs. */
10791108
return 0;
@@ -3046,7 +3075,7 @@ static zend_always_inline int i_zend_verify_type_assignable_zval(
30463075
}
30473076

30483077
type_mask = ZEND_TYPE_FULL_MASK(type);
3049-
ZEND_ASSERT(!(type_mask & MAY_BE_CALLABLE));
3078+
ZEND_ASSERT(!(type_mask & (MAY_BE_CALLABLE|MAY_BE_STATIC)));
30503079
if (type_mask & MAY_BE_ITERABLE) {
30513080
return zend_is_iterable(zv);
30523081
}

Zend/zend_execute.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ ZEND_API ZEND_COLD void zend_verify_arg_error(
6767
ZEND_API ZEND_COLD void zend_verify_return_error(
6868
const zend_function *zf, void **cache_slot, zval *value);
6969
ZEND_API zend_bool zend_verify_ref_array_assignable(zend_reference *ref);
70+
ZEND_API zend_bool zend_value_instanceof_static(zval *zv);
71+
7072

7173
#define ZEND_REF_TYPE_SOURCES(ref) \
7274
(ref)->sources

0 commit comments

Comments
 (0)