Skip to content

Commit e7a9b81

Browse files
committed
Add static return type
1 parent 1e4920c commit e7a9b81

16 files changed

+270
-24
lines changed
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: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
?>
56+
--EXPECT--
57+
object(A)#3 (0) {
58+
}
59+
object(B)#3 (0) {
60+
}
61+
62+
object(A)#3 (0) {
63+
}
64+
Return value of A::test2() must be an instance of static, instance of A returned
65+
66+
object(A)#3 (0) {
67+
}
68+
object(C)#3 (0) {
69+
}
70+
71+
object(A)#3 (0) {
72+
}
73+
Return value of A::test4() must be of type static|array, instance of A returned
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: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,9 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
11801180
}
11811181

11821182
uint32_t type_mask = ZEND_TYPE_FULL_MASK(type);
1183+
if (type_mask & MAY_BE_STATIC) {
1184+
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_STATIC));
1185+
}
11831186
if (type_mask & MAY_BE_CALLABLE) {
11841187
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE));
11851188
}
@@ -5618,7 +5621,7 @@ static zend_type zend_compile_typename(
56185621
ZSTR_VAL(type_str));
56195622
}
56205623

5621-
if ((type_mask & MAY_BE_OBJECT) && ZEND_TYPE_HAS_CLASS(type)) {
5624+
if ((type_mask & MAY_BE_OBJECT) && (ZEND_TYPE_HAS_CLASS(type) || (type_mask & MAY_BE_STATIC))) {
56225625
zend_string *type_str = zend_type_to_string(type);
56235626
zend_error_noreturn(E_COMPILE_ERROR,
56245627
"Type %s contains both object and a class type, which is redundant",

Zend/zend_execute.c

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,9 @@ 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+
smart_str_appends(&str, "be an instance of static");
719+
break;
717720
default:
718721
{
719722
/* Hack to print the type without null */
@@ -735,7 +738,9 @@ static ZEND_COLD void zend_verify_type_error_common(
735738
*need_msg = smart_str_extract(&str);
736739

737740
if (value) {
738-
if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(value) == IS_OBJECT) {
741+
zend_bool has_class = ZEND_TYPE_HAS_CLASS(arg_info->type)
742+
|| (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_STATIC);
743+
if (has_class && Z_TYPE_P(value) == IS_OBJECT) {
739744
*given_msg = "instance of ";
740745
*given_kind = ZSTR_VAL(Z_OBJCE_P(value)->name);
741746
} else {
@@ -988,11 +993,12 @@ static zend_always_inline zend_bool i_zend_check_property_type(zend_property_inf
988993
return 1;
989994
}
990995

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)) {
996+
uint32_t type_mask = ZEND_TYPE_FULL_MASK(info->type);
997+
ZEND_ASSERT(!(type_mask & (MAY_BE_CALLABLE|MAY_BE_STATIC)));
998+
if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(property)) {
993999
return 1;
9941000
}
995-
return zend_verify_scalar_type_hint(ZEND_TYPE_FULL_MASK(info->type), property, strict, 0);
1001+
return zend_verify_scalar_type_hint(type_mask, property, strict, 0);
9961002
}
9971003

9981004
static zend_bool zend_always_inline i_zend_verify_property_type(zend_property_info *info, zval *property, zend_bool strict)
@@ -1024,6 +1030,19 @@ static zend_never_inline zval* zend_assign_to_typed_prop(zend_property_info *inf
10241030
return zend_assign_to_variable(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES());
10251031
}
10261032

1033+
ZEND_API zend_bool zend_value_instanceof_static(zval *zv) {
1034+
if (Z_TYPE_P(zv) != IS_OBJECT) {
1035+
return 0;
1036+
}
1037+
1038+
zend_class_entry *called_scope = zend_get_called_scope(EG(current_execute_data));
1039+
if (!called_scope) {
1040+
return 0;
1041+
}
1042+
return instanceof_function(Z_OBJCE_P(zv), called_scope);
1043+
}
1044+
1045+
10271046
static zend_always_inline zend_bool zend_check_type_slow(
10281047
zend_type type, zval *arg, zend_reference *ref, void **cache_slot, zend_class_entry *scope,
10291048
zend_bool is_return_type, zend_bool is_internal)
@@ -1074,6 +1093,9 @@ static zend_always_inline zend_bool zend_check_type_slow(
10741093
if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) {
10751094
return 1;
10761095
}
1096+
if ((type_mask & MAY_BE_STATIC) && zend_value_instanceof_static(arg)) {
1097+
return 1;
1098+
}
10771099
if (ref && ZEND_REF_HAS_TYPE_SOURCES(ref)) {
10781100
/* We cannot have conversions for typed refs. */
10791101
return 0;
@@ -3042,7 +3064,7 @@ static zend_always_inline int i_zend_verify_type_assignable_zval(
30423064
}
30433065

30443066
type_mask = ZEND_TYPE_FULL_MASK(type);
3045-
ZEND_ASSERT(!(type_mask & MAY_BE_CALLABLE));
3067+
ZEND_ASSERT(!(type_mask & (MAY_BE_CALLABLE|MAY_BE_STATIC)));
30463068
if (type_mask & MAY_BE_ITERABLE) {
30473069
return zend_is_iterable(zv);
30483070
}

Zend/zend_execute.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ ZEND_API ZEND_COLD void zend_verify_arg_error(
6565
const zend_function *zf, const zend_arg_info *arg_info,
6666
int arg_num, void **cache_slot, zval *value);
6767
ZEND_API zend_bool zend_verify_ref_array_assignable(zend_reference *ref);
68+
ZEND_API zend_bool zend_value_instanceof_static(zval *zv);
69+
6870

6971
#define ZEND_REF_TYPE_SOURCES(ref) \
7072
(ref)->sources

Zend/zend_inheritance.c

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,34 @@ static zend_bool zend_type_contains_traversable(zend_type type) {
339339
return 0;
340340
}
341341

342+
static zend_bool zend_type_permits_self(
343+
zend_type type, zend_class_entry *scope, zend_class_entry *self) {
344+
if (ZEND_TYPE_FULL_MASK(type) & MAY_BE_OBJECT) {
345+
return 1;
346+
}
347+
348+
/* Any types that may satisfy self must have already been loaded at this point
349+
* (as a parent or interface), so we never need to register delayed variance obligations
350+
* for this case. */
351+
if (ZEND_TYPE_HAS_LIST(type)) {
352+
void *entry;
353+
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) {
354+
zend_string *name = resolve_class_name(scope, ZEND_TYPE_LIST_GET_NAME(entry));
355+
zend_class_entry *ce = lookup_class(self, name, /* register_unresolved */ 0);
356+
if (ce && unlinked_instanceof(self, ce)) {
357+
return 1;
358+
}
359+
} ZEND_TYPE_LIST_FOREACH_END();
360+
return 0;
361+
}
362+
if (ZEND_TYPE_HAS_NAME(type)) {
363+
zend_string *name = resolve_class_name(scope, ZEND_TYPE_NAME(type));
364+
zend_class_entry *ce = lookup_class(self, name, /* register_unresolved */ 0);
365+
return ce && unlinked_instanceof(self, ce);
366+
}
367+
return 0;
368+
}
369+
342370
/* Unresolved means that class declarations that are currently not available are needed to
343371
* determine whether the inheritance is valid or not. At runtime UNRESOLVED should be treated
344372
* as an ERROR. */
@@ -426,13 +454,22 @@ static inheritance_status zend_perform_covariant_type_check(
426454
if (added_types) {
427455
// TODO: Make "iterable" an alias of "array|Traversable" instead,
428456
// so these special cases will be handled automatically.
429-
if (added_types == MAY_BE_ITERABLE
457+
if ((added_types & MAY_BE_ITERABLE)
430458
&& (proto_type_mask & MAY_BE_ARRAY)
431459
&& zend_type_contains_traversable(proto_type)) {
432460
/* Replacing array|Traversable with iterable is okay */
433-
} else if (added_types == MAY_BE_ARRAY && (proto_type_mask & MAY_BE_ITERABLE)) {
461+
added_types &= ~MAY_BE_ITERABLE;
462+
}
463+
if ((added_types & MAY_BE_ARRAY) && (proto_type_mask & MAY_BE_ITERABLE)) {
434464
/* Replacing iterable with array is okay */
435-
} else {
465+
added_types &= ~MAY_BE_ARRAY;
466+
}
467+
if ((added_types & MAY_BE_STATIC)
468+
&& zend_type_permits_self(proto_type, proto_scope, fe_scope)) {
469+
/* Replacing type that accepts self with static is okay */
470+
added_types &= ~MAY_BE_STATIC;
471+
}
472+
if (added_types) {
436473
/* Otherwise adding new types is illegal */
437474
return INHERITANCE_ERROR;
438475
}

0 commit comments

Comments
 (0)