summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--UPGRADING9
-rw-r--r--Zend/tests/type_declarations/static_type_in_final_class.phpt17
-rw-r--r--Zend/tests/type_declarations/static_type_outside_class.phpt10
-rw-r--r--Zend/tests/type_declarations/static_type_param.phpt15
-rw-r--r--Zend/tests/type_declarations/static_type_property.phpt13
-rw-r--r--Zend/tests/type_declarations/static_type_return.phpt91
-rw-r--r--Zend/tests/type_declarations/static_type_trait.phpt38
-rw-r--r--Zend/tests/type_declarations/union_types/redundant_types/object_and_static.phpt12
-rw-r--r--Zend/tests/type_declarations/variance/static_variance_failure.phpt15
-rw-r--r--Zend/tests/type_declarations/variance/static_variance_success.phpt25
-rw-r--r--Zend/zend_compile.c16
-rw-r--r--Zend/zend_execute.c41
-rw-r--r--Zend/zend_execute.h2
-rw-r--r--Zend/zend_inheritance.c37
-rw-r--r--Zend/zend_language_parser.y49
-rw-r--r--Zend/zend_type_info.h1
-rw-r--r--Zend/zend_types.h1
-rw-r--r--ext/opcache/Optimizer/dfa_pass.c2
-rw-r--r--ext/opcache/Optimizer/zend_inference.c3
-rw-r--r--ext/opcache/jit/zend_jit_helpers.c3
-rw-r--r--ext/reflection/php_reflection.c4
-rw-r--r--ext/reflection/tests/static_type.phpt22
22 files changed, 400 insertions, 26 deletions
diff --git a/UPGRADING b/UPGRADING
index e71bc5332a..30611b1013 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -367,6 +367,15 @@ PHP 8.0 UPGRADE NOTES
class B extends A {
public function method(...$everything) {}
}
+ . "static" (as in "late static binding") can now be used as a return type:
+
+ class Test {
+ public function create(): static {
+ return new static();
+ }
+ }
+
+ RFC: https://wiki.php.net/rfc/static_return_type
. It is now possible to fetch the class name of an object using
`$object::class`. The result is the same as `get_class($object)`.
RFC: https://wiki.php.net/rfc/class_name_literal_on_object
diff --git a/Zend/tests/type_declarations/static_type_in_final_class.phpt b/Zend/tests/type_declarations/static_type_in_final_class.phpt
new file mode 100644
index 0000000000..2954d7d1ac
--- /dev/null
+++ b/Zend/tests/type_declarations/static_type_in_final_class.phpt
@@ -0,0 +1,17 @@
+--TEST--
+While it may not be very useful, static is also permitted in final classes
+--FILE--
+<?php
+
+final class Test {
+ public static function create(): static {
+ return new static;
+ }
+}
+
+var_dump(Test::create());
+
+?>
+--EXPECT--
+object(Test)#1 (0) {
+}
diff --git a/Zend/tests/type_declarations/static_type_outside_class.phpt b/Zend/tests/type_declarations/static_type_outside_class.phpt
new file mode 100644
index 0000000000..f485e394b4
--- /dev/null
+++ b/Zend/tests/type_declarations/static_type_outside_class.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Static type outside class generates compile error
+--FILE--
+<?php
+
+function test(): static {}
+
+?>
+--EXPECTF--
+Fatal error: Cannot use "static" when no class scope is active in %s on line %d
diff --git a/Zend/tests/type_declarations/static_type_param.phpt b/Zend/tests/type_declarations/static_type_param.phpt
new file mode 100644
index 0000000000..4c0d6b1dfc
--- /dev/null
+++ b/Zend/tests/type_declarations/static_type_param.phpt
@@ -0,0 +1,15 @@
+--TEST--
+Static type is not allowed in parameters
+--FILE--
+<?php
+
+// TODO: We could prohibit this case in the compiler instead.
+
+class Test {
+ public function test(static $param) {
+ }
+}
+
+?>
+--EXPECTF--
+Parse error: syntax error, unexpected 'static' (T_STATIC), expecting variable (T_VARIABLE) in %s on line %d
diff --git a/Zend/tests/type_declarations/static_type_property.phpt b/Zend/tests/type_declarations/static_type_property.phpt
new file mode 100644
index 0000000000..aba303d5fa
--- /dev/null
+++ b/Zend/tests/type_declarations/static_type_property.phpt
@@ -0,0 +1,13 @@
+--TEST--
+Static type is not allowed in properties
+--FILE--
+<?php
+
+// Testing ?static here, to avoid ambiguity with static properties.
+class Test {
+ public ?static $foo;
+}
+
+?>
+--EXPECTF--
+Parse error: syntax error, unexpected 'static' (T_STATIC) in %s on line %d
diff --git a/Zend/tests/type_declarations/static_type_return.phpt b/Zend/tests/type_declarations/static_type_return.phpt
new file mode 100644
index 0000000000..4a04cfff78
--- /dev/null
+++ b/Zend/tests/type_declarations/static_type_return.phpt
@@ -0,0 +1,91 @@
+--TEST--
+Static return type
+--FILE--
+<?php
+
+class A {
+ public function test1(): static {
+ return new static;
+ }
+ public function test2(): static {
+ return new self;
+ }
+ public function test3(): static {
+ return new static;
+ }
+ public function test4(): static|array {
+ return new self;
+ }
+}
+
+class B extends A {
+ public function test3(): static {
+ return new C;
+ }
+}
+
+class C extends B {}
+
+$a = new A;
+$b = new B;
+
+var_dump($a->test1());
+var_dump($b->test1());
+
+echo "\n";
+var_dump($a->test2());
+try {
+ var_dump($b->test2());
+} catch (TypeError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+echo "\n";
+var_dump($a->test3());
+var_dump($b->test3());
+
+echo "\n";
+var_dump($a->test4());
+try {
+ var_dump($b->test4());
+} catch (TypeError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+echo "\n";
+$test = function($x): static {
+ return $x;
+};
+
+try {
+ var_dump($test(new stdClass));
+} catch (TypeError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+$test = $test->bindTo($a);
+var_dump($test($a));
+
+?>
+--EXPECT--
+object(A)#3 (0) {
+}
+object(B)#3 (0) {
+}
+
+object(A)#3 (0) {
+}
+Return value of A::test2() must be an instance of B, instance of A returned
+
+object(A)#3 (0) {
+}
+object(C)#3 (0) {
+}
+
+object(A)#3 (0) {
+}
+Return value of A::test4() must be of type B|array, instance of A returned
+
+Return value of {closure}() must be an instance of static, instance of stdClass returned
+object(A)#1 (0) {
+}
diff --git a/Zend/tests/type_declarations/static_type_trait.phpt b/Zend/tests/type_declarations/static_type_trait.phpt
new file mode 100644
index 0000000000..ede9fd62b1
--- /dev/null
+++ b/Zend/tests/type_declarations/static_type_trait.phpt
@@ -0,0 +1,38 @@
+--TEST--
+static type in trait
+--FILE--
+<?php
+
+trait T {
+ public function test($arg): static {
+ return $arg;
+ }
+}
+
+class C {
+ use T;
+}
+class P extends C {
+}
+
+$c = new C;
+$p = new P;
+var_dump($c->test($c));
+var_dump($c->test($p));
+var_dump($p->test($p));
+var_dump($p->test($c));
+
+?>
+--EXPECTF--
+object(C)#1 (0) {
+}
+object(P)#2 (0) {
+}
+object(P)#2 (0) {
+}
+
+Fatal error: Uncaught TypeError: Return value of C::test() must be an instance of P, instance of C returned in %s:%d
+Stack trace:
+#0 %s(%d): C->test(Object(C))
+#1 {main}
+ thrown in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/redundant_types/object_and_static.phpt b/Zend/tests/type_declarations/union_types/redundant_types/object_and_static.phpt
new file mode 100644
index 0000000000..f4152c2a23
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/redundant_types/object_and_static.phpt
@@ -0,0 +1,12 @@
+--TEST--
+object and static are redundant
+--FILE--
+<?php
+
+class Test {
+ public function foo(): static|object {}
+}
+
+?>
+--EXPECTF--
+Fatal error: Type static|object contains both object and a class type, which is redundant in %s on line %d
diff --git a/Zend/tests/type_declarations/variance/static_variance_failure.phpt b/Zend/tests/type_declarations/variance/static_variance_failure.phpt
new file mode 100644
index 0000000000..159a69c306
--- /dev/null
+++ b/Zend/tests/type_declarations/variance/static_variance_failure.phpt
@@ -0,0 +1,15 @@
+--TEST--
+Failure case for static variance: Replace static with self
+--FILE--
+<?php
+
+class A {
+ public function test(): static {}
+}
+class B extends A {
+ public function test(): self {}
+}
+
+?>
+--EXPECTF--
+Fatal error: Declaration of B::test(): B must be compatible with A::test(): static in %s on line %d
diff --git a/Zend/tests/type_declarations/variance/static_variance_success.phpt b/Zend/tests/type_declarations/variance/static_variance_success.phpt
new file mode 100644
index 0000000000..3749d8d874
--- /dev/null
+++ b/Zend/tests/type_declarations/variance/static_variance_success.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Success cases for static variance
+--FILE--
+<?php
+
+class A {
+ public function test1(): self {}
+ public function test2(): B {}
+ public function test3(): object {}
+ public function test4(): X|Y|self {}
+ public function test5(): ?static {}
+}
+
+class B extends A {
+ public function test1(): static {}
+ public function test2(): static {}
+ public function test3(): static {}
+ public function test4(): X|Y|static {}
+ public function test5(): static {}
+}
+
+?>
+===DONE===
+--EXPECT--
+===DONE===
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index d43bb6f4a3..58fb3377d2 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -1196,6 +1196,16 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
}
uint32_t type_mask = ZEND_TYPE_FULL_MASK(type);
+ if (type_mask & MAY_BE_STATIC) {
+ zend_string *name = ZSTR_KNOWN(ZEND_STR_STATIC);
+ if (scope) {
+ zend_class_entry *called_scope = zend_get_called_scope(EG(current_execute_data));
+ if (called_scope) {
+ name = called_scope->name;
+ }
+ }
+ str = add_type_string(str, name);
+ }
if (type_mask & MAY_BE_CALLABLE) {
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE));
}
@@ -5502,6 +5512,10 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
{
ZEND_ASSERT(!(ast->attr & ZEND_TYPE_NULLABLE));
if (ast->kind == ZEND_AST_TYPE) {
+ if (ast->attr == IS_STATIC && !CG(active_class_entry) && zend_is_scope_known()) {
+ zend_error_noreturn(E_COMPILE_ERROR,
+ "Cannot use \"static\" when no class scope is active");
+ }
return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, 0, 0);
} else {
zend_string *class_name = zend_ast_get_str(ast);
@@ -5657,7 +5671,7 @@ static zend_type zend_compile_typename(
ZSTR_VAL(type_str));
}
- if ((type_mask & MAY_BE_OBJECT) && ZEND_TYPE_HAS_CLASS(type)) {
+ if ((type_mask & MAY_BE_OBJECT) && (ZEND_TYPE_HAS_CLASS(type) || (type_mask & MAY_BE_STATIC))) {
zend_string *type_str = zend_type_to_string(type);
zend_error_noreturn(E_COMPILE_ERROR,
"Type %s contains both object and a class type, which is redundant",
diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c
index 140038e4c3..3fbbcef0fe 100644
--- a/Zend/zend_execute.c
+++ b/Zend/zend_execute.c
@@ -674,7 +674,7 @@ static ZEND_COLD void zend_verify_type_error_common(
}
if (is_union_type(arg_info->type)) {
- zend_string *type_str = zend_type_to_string(arg_info->type);
+ zend_string *type_str = zend_type_to_string_resolved(arg_info->type, zf->common.scope);
smart_str_appends(&str, "be of type ");
smart_str_append(&str, type_str);
zend_string_release(type_str);
@@ -714,6 +714,16 @@ static ZEND_COLD void zend_verify_type_error_common(
case MAY_BE_ITERABLE:
smart_str_appends(&str, "be iterable");
break;
+ case MAY_BE_STATIC: {
+ zend_class_entry *called_scope = zend_get_called_scope(EG(current_execute_data));
+ smart_str_appends(&str, "be an instance of ");
+ if (called_scope) {
+ smart_str_append(&str, called_scope->name);
+ } else {
+ smart_str_appends(&str, "static");
+ }
+ break;
+ }
default:
{
/* Hack to print the type without null */
@@ -735,7 +745,9 @@ static ZEND_COLD void zend_verify_type_error_common(
*need_msg = smart_str_extract(&str);
if (value) {
- if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(value) == IS_OBJECT) {
+ zend_bool has_class = ZEND_TYPE_HAS_CLASS(arg_info->type)
+ || (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_STATIC);
+ if (has_class && Z_TYPE_P(value) == IS_OBJECT) {
*given_msg = "instance of ";
*given_kind = ZSTR_VAL(Z_OBJCE_P(value)->name);
} else {
@@ -988,11 +1000,12 @@ static zend_always_inline zend_bool i_zend_check_property_type(zend_property_inf
return 1;
}
- ZEND_ASSERT(!(ZEND_TYPE_FULL_MASK(info->type) & MAY_BE_CALLABLE));
- if ((ZEND_TYPE_FULL_MASK(info->type) & MAY_BE_ITERABLE) && zend_is_iterable(property)) {
+ uint32_t type_mask = ZEND_TYPE_FULL_MASK(info->type);
+ ZEND_ASSERT(!(type_mask & (MAY_BE_CALLABLE|MAY_BE_STATIC)));
+ if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(property)) {
return 1;
}
- return zend_verify_scalar_type_hint(ZEND_TYPE_FULL_MASK(info->type), property, strict, 0);
+ return zend_verify_scalar_type_hint(type_mask, property, strict, 0);
}
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
return zend_assign_to_variable(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES());
}
+ZEND_API zend_bool zend_value_instanceof_static(zval *zv) {
+ if (Z_TYPE_P(zv) != IS_OBJECT) {
+ return 0;
+ }
+
+ zend_class_entry *called_scope = zend_get_called_scope(EG(current_execute_data));
+ if (!called_scope) {
+ return 0;
+ }
+ return instanceof_function(Z_OBJCE_P(zv), called_scope);
+}
+
+
static zend_always_inline zend_bool zend_check_type_slow(
zend_type type, zval *arg, zend_reference *ref, void **cache_slot, zend_class_entry *scope,
zend_bool is_return_type, zend_bool is_internal)
@@ -1074,6 +1100,9 @@ builtin_types:
if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) {
return 1;
}
+ if ((type_mask & MAY_BE_STATIC) && zend_value_instanceof_static(arg)) {
+ return 1;
+ }
if (ref && ZEND_REF_HAS_TYPE_SOURCES(ref)) {
/* We cannot have conversions for typed refs. */
return 0;
@@ -3046,7 +3075,7 @@ static zend_always_inline int i_zend_verify_type_assignable_zval(
}
type_mask = ZEND_TYPE_FULL_MASK(type);
- ZEND_ASSERT(!(type_mask & MAY_BE_CALLABLE));
+ ZEND_ASSERT(!(type_mask & (MAY_BE_CALLABLE|MAY_BE_STATIC)));
if (type_mask & MAY_BE_ITERABLE) {
return zend_is_iterable(zv);
}
diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h
index 44b48e914c..f29a5eb050 100644
--- a/Zend/zend_execute.h
+++ b/Zend/zend_execute.h
@@ -67,6 +67,8 @@ ZEND_API ZEND_COLD void zend_verify_arg_error(
ZEND_API ZEND_COLD void zend_verify_return_error(
const zend_function *zf, void **cache_slot, zval *value);
ZEND_API zend_bool zend_verify_ref_array_assignable(zend_reference *ref);
+ZEND_API zend_bool zend_value_instanceof_static(zval *zv);
+
#define ZEND_REF_TYPE_SOURCES(ref) \
(ref)->sources
diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c
index 5abe1c55d4..219c1ffcc9 100644
--- a/Zend/zend_inheritance.c
+++ b/Zend/zend_inheritance.c
@@ -333,6 +333,28 @@ static zend_bool zend_type_contains_traversable(zend_type type) {
return 0;
}
+static zend_bool zend_type_permits_self(
+ zend_type type, zend_class_entry *scope, zend_class_entry *self) {
+ if (ZEND_TYPE_FULL_MASK(type) & MAY_BE_OBJECT) {
+ return 1;
+ }
+
+ /* Any types that may satisfy self must have already been loaded at this point
+ * (as a parent or interface), so we never need to register delayed variance obligations
+ * for this case. */
+ zend_type *single_type;
+ ZEND_TYPE_FOREACH(type, single_type) {
+ if (ZEND_TYPE_HAS_NAME(*single_type)) {
+ zend_string *name = resolve_class_name(scope, ZEND_TYPE_NAME(*single_type));
+ zend_class_entry *ce = lookup_class(self, name, /* register_unresolved */ 0);
+ if (ce && unlinked_instanceof(self, ce)) {
+ return 1;
+ }
+ }
+ } ZEND_TYPE_FOREACH_END();
+ return 0;
+}
+
/* Unresolved means that class declarations that are currently not available are needed to
* determine whether the inheritance is valid or not. At runtime UNRESOLVED should be treated
* as an ERROR. */
@@ -406,13 +428,22 @@ static inheritance_status zend_perform_covariant_type_check(
if (added_types) {
// TODO: Make "iterable" an alias of "array|Traversable" instead,
// so these special cases will be handled automatically.
- if (added_types == MAY_BE_ITERABLE
+ if ((added_types & MAY_BE_ITERABLE)
&& (proto_type_mask & MAY_BE_ARRAY)
&& zend_type_contains_traversable(proto_type)) {
/* Replacing array|Traversable with iterable is okay */
- } else if (added_types == MAY_BE_ARRAY && (proto_type_mask & MAY_BE_ITERABLE)) {
+ added_types &= ~MAY_BE_ITERABLE;
+ }
+ if ((added_types & MAY_BE_ARRAY) && (proto_type_mask & MAY_BE_ITERABLE)) {
/* Replacing iterable with array is okay */
- } else {
+ added_types &= ~MAY_BE_ARRAY;
+ }
+ if ((added_types & MAY_BE_STATIC)
+ && zend_type_permits_self(proto_type, proto_scope, fe_scope)) {
+ /* Replacing type that accepts self with static is okay */
+ added_types &= ~MAY_BE_STATIC;
+ }
+ if (added_types) {
/* Otherwise adding new types is illegal */
return INHERITANCE_ERROR;
}
diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y
index 02dbd091dc..c1ced9a35a 100644
--- a/Zend/zend_language_parser.y
+++ b/Zend/zend_language_parser.y
@@ -235,7 +235,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%type <ast> unprefixed_use_declarations const_decl inner_statement
%type <ast> expr optional_expr while_statement for_statement foreach_variable
%type <ast> foreach_statement declare_statement finally_statement unset_variable variable
-%type <ast> extends_from parameter optional_type argument global_var
+%type <ast> extends_from parameter optional_type_without_static argument global_var
%type <ast> static_var class_statement trait_adaptation trait_precedence trait_alias
%type <ast> absolute_trait_method_reference trait_method_reference property echo_expr
%type <ast> new_expr anonymous_class class_name class_name_reference simple_variable
@@ -254,8 +254,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%type <ast> ctor_arguments alt_if_stmt_without_else trait_adaptation_list lexical_vars
%type <ast> lexical_var_list encaps_list
%type <ast> array_pair non_empty_array_pair_list array_pair_list possible_array_pair
-%type <ast> isset_variable type return_type type_expr
-%type <ast> identifier
+%type <ast> isset_variable type return_type type_expr type_without_static
+%type <ast> identifier type_expr_without_static union_type_without_static
%type <ast> inline_function union_type
%type <num> returns_ref function fn is_reference is_variadic variable_modifiers
@@ -646,28 +646,27 @@ non_empty_parameter_list:
;
parameter:
- optional_type is_reference is_variadic T_VARIABLE
+ optional_type_without_static is_reference is_variadic T_VARIABLE
{ $$ = zend_ast_create_ex(ZEND_AST_PARAM, $2 | $3, $1, $4, NULL); }
- | optional_type is_reference is_variadic T_VARIABLE '=' expr
+ | optional_type_without_static is_reference is_variadic T_VARIABLE '=' expr
{ $$ = zend_ast_create_ex(ZEND_AST_PARAM, $2 | $3, $1, $4, $6); }
;
-optional_type:
+optional_type_without_static:
%empty { $$ = NULL; }
- | type_expr { $$ = $1; }
+ | type_expr_without_static { $$ = $1; }
;
type_expr:
- type { $$ = $1; }
- | '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; }
- | union_type { $$ = $1; }
+ type { $$ = $1; }
+ | '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; }
+ | union_type { $$ = $1; }
;
type:
- T_ARRAY { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); }
- | T_CALLABLE { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_CALLABLE); }
- | name { $$ = $1; }
+ type_without_static { $$ = $1; }
+ | T_STATIC { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_STATIC); }
;
union_type:
@@ -675,6 +674,28 @@ union_type:
| union_type '|' type { $$ = zend_ast_list_add($1, $3); }
;
+/* Duplicate the type rules without "static",
+ * to avoid conflicts with "static" modifier for properties. */
+
+type_expr_without_static:
+ type_without_static { $$ = $1; }
+ | '?' type_without_static { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; }
+ | union_type_without_static { $$ = $1; }
+;
+
+type_without_static:
+ T_ARRAY { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); }
+ | T_CALLABLE { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_CALLABLE); }
+ | name { $$ = $1; }
+;
+
+union_type_without_static:
+ type_without_static '|' type_without_static
+ { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); }
+ | union_type_without_static '|' type_without_static
+ { $$ = zend_ast_list_add($1, $3); }
+;
+
return_type:
%empty { $$ = NULL; }
| ':' type_expr { $$ = $2; }
@@ -728,7 +749,7 @@ class_statement_list:
class_statement:
- variable_modifiers optional_type property_list ';'
+ variable_modifiers optional_type_without_static property_list ';'
{ $$ = zend_ast_create(ZEND_AST_PROP_GROUP, $2, $3);
$$->attr = $1; }
| method_modifiers T_CONST class_const_list ';'
diff --git a/Zend/zend_type_info.h b/Zend/zend_type_info.h
index 752e6a4b85..bace8014bf 100644
--- a/Zend/zend_type_info.h
+++ b/Zend/zend_type_info.h
@@ -40,6 +40,7 @@
#define MAY_BE_CALLABLE (1 << IS_CALLABLE)
#define MAY_BE_ITERABLE (1 << IS_ITERABLE)
#define MAY_BE_VOID (1 << IS_VOID)
+#define MAY_BE_STATIC (1 << IS_STATIC)
#define MAY_BE_ARRAY_SHIFT (IS_REFERENCE)
diff --git a/Zend/zend_types.h b/Zend/zend_types.h
index 1b314afa24..d8b2280e47 100644
--- a/Zend/zend_types.h
+++ b/Zend/zend_types.h
@@ -533,6 +533,7 @@ struct _zend_ast_ref {
#define IS_CALLABLE 12
#define IS_ITERABLE 13
#define IS_VOID 14
+#define IS_STATIC 15
/* internal types */
#define IS_INDIRECT 12
diff --git a/ext/opcache/Optimizer/dfa_pass.c b/ext/opcache/Optimizer/dfa_pass.c
index 8802577154..e27826b632 100644
--- a/ext/opcache/Optimizer/dfa_pass.c
+++ b/ext/opcache/Optimizer/dfa_pass.c
@@ -307,7 +307,7 @@ static inline zend_bool can_elide_return_type_check(
}
/* These types are not represented exactly */
- if (ZEND_TYPE_FULL_MASK(info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) {
+ if (ZEND_TYPE_FULL_MASK(info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE|MAY_BE_STATIC)) {
return 0;
}
diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c
index 90273e2ffd..4675cfe99b 100644
--- a/ext/opcache/Optimizer/zend_inference.c
+++ b/ext/opcache/Optimizer/zend_inference.c
@@ -2223,6 +2223,9 @@ static uint32_t zend_convert_type_declaration_mask(uint32_t type_mask) {
if (type_mask & MAY_BE_ITERABLE) {
result_mask |= MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
}
+ if (type_mask & MAY_BE_STATIC) {
+ result_mask |= MAY_BE_OBJECT;
+ }
if (type_mask & MAY_BE_ARRAY) {
result_mask |= MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
}
diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c
index d0caabe5da..7fd91c7317 100644
--- a/ext/opcache/jit/zend_jit_helpers.c
+++ b/ext/opcache/jit/zend_jit_helpers.c
@@ -1190,6 +1190,9 @@ builtin_types:
if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) {
return 1;
}
+ if ((type_mask & MAY_BE_STATIC) && zend_value_instanceof_static(arg)) {
+ return 1;
+ }
if (zend_verify_scalar_type_hint(type_mask, arg, ZEND_ARG_USES_STRICT_TYPES(), /* is_internal */ 0)) {
return 1;
}
diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c
index 95c4859e94..8d2d53b028 100644
--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -2909,7 +2909,9 @@ ZEND_METHOD(reflection_named_type, isBuiltin)
}
GET_REFLECTION_OBJECT_PTR(param);
- RETVAL_BOOL(ZEND_TYPE_IS_ONLY_MASK(param->type));
+ /* Treat "static" as a class type for the purposes of reflection. */
+ RETVAL_BOOL(ZEND_TYPE_IS_ONLY_MASK(param->type)
+ && !(ZEND_TYPE_FULL_MASK(param->type) & MAY_BE_STATIC));
}
/* }}} */
diff --git a/ext/reflection/tests/static_type.phpt b/ext/reflection/tests/static_type.phpt
new file mode 100644
index 0000000000..b541008f79
--- /dev/null
+++ b/ext/reflection/tests/static_type.phpt
@@ -0,0 +1,22 @@
+--TEST--
+ReflectionType for static types
+--FILE--
+<?php
+
+class A {
+ public function test(): static {
+ return new static;
+ }
+}
+
+$rm = new ReflectionMethod(A::class, 'test');
+$rt = $rm->getReturnType();
+var_dump($rt->isBuiltin());
+var_dump($rt->getName());
+var_dump((string) $rt);
+
+?>
+--EXPECT--
+bool(false)
+string(6) "static"
+string(6) "static"