]> granicus.if.org Git - php/commitdiff
Add static return type
authorNikita Popov <nikita.ppv@gmail.com>
Tue, 7 Jan 2020 14:06:36 +0000 (15:06 +0100)
committerNikita Popov <nikita.ppv@gmail.com>
Mon, 17 Feb 2020 10:51:09 +0000 (11:51 +0100)
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.

22 files changed:
UPGRADING
Zend/tests/type_declarations/static_type_in_final_class.phpt [new file with mode: 0644]
Zend/tests/type_declarations/static_type_outside_class.phpt [new file with mode: 0644]
Zend/tests/type_declarations/static_type_param.phpt [new file with mode: 0644]
Zend/tests/type_declarations/static_type_property.phpt [new file with mode: 0644]
Zend/tests/type_declarations/static_type_return.phpt [new file with mode: 0644]
Zend/tests/type_declarations/static_type_trait.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/redundant_types/object_and_static.phpt [new file with mode: 0644]
Zend/tests/type_declarations/variance/static_variance_failure.phpt [new file with mode: 0644]
Zend/tests/type_declarations/variance/static_variance_success.phpt [new file with mode: 0644]
Zend/zend_compile.c
Zend/zend_execute.c
Zend/zend_execute.h
Zend/zend_inheritance.c
Zend/zend_language_parser.y
Zend/zend_type_info.h
Zend/zend_types.h
ext/opcache/Optimizer/dfa_pass.c
ext/opcache/Optimizer/zend_inference.c
ext/opcache/jit/zend_jit_helpers.c
ext/reflection/php_reflection.c
ext/reflection/tests/static_type.phpt [new file with mode: 0644]

index e71bc5332a215a8e7eda21ee9b9be65bc09592d1..30611b10132190c8aed92d12e8eae496b988f452 100644 (file)
--- 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 (file)
index 0000000..2954d7d
--- /dev/null
@@ -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 (file)
index 0000000..f485e39
--- /dev/null
@@ -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 (file)
index 0000000..4c0d6b1
--- /dev/null
@@ -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 (file)
index 0000000..aba303d
--- /dev/null
@@ -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 (file)
index 0000000..4a04cff
--- /dev/null
@@ -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 (file)
index 0000000..ede9fd6
--- /dev/null
@@ -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 (file)
index 0000000..f4152c2
--- /dev/null
@@ -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 (file)
index 0000000..159a69c
--- /dev/null
@@ -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 (file)
index 0000000..3749d8d
--- /dev/null
@@ -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===
index d43bb6f4a34f09ef0ef0cfd439d424665c158e0e..58fb3377d2cfc4a653b31e74d6b049d446051416 100644 (file)
@@ -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",
index 140038e4c333a4f9bfbf11f39b687069179f552c..3fbbcef0feb67704c5931d1d43b1892bf8f8d0f7 100644 (file)
@@ -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);
        }
index 44b48e914c3d72b4d317265f41ce0fd2e233e017..f29a5eb050229523a31dd0b9805cc3447e7f16cc 100644 (file)
@@ -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
index 5abe1c55d4a0c490514329215ad11db907c92283..219c1ffcc9f060866f192a5f0cc4cd0cbd5ff6b9 100644 (file)
@@ -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;
                }
index 02dbd091dcf89858a95254d3a489e4b09a1dd3bc..c1ced9a35aa21cd78fbe9f63d0e13e5587ec5d50 100644 (file)
@@ -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 ';'
index 752e6a4b850e9cbfef1e2eab7287a90d44795372..bace8014bfc3fec2b89a299f5954e9061da6d424 100644 (file)
@@ -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)
 
index 1b314afa24287460ee552d18006858cae151d931..d8b2280e47f8bd4a9e1ddf3c0427dd4e1ffffbfd 100644 (file)
@@ -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
index 8802577154269efed92f5d10d2067f46668f00fd..e27826b6327b2972a195139b41016d09d8d43217 100644 (file)
@@ -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;
        }
 
index 90273e2ffdc2dd127e61ccd0931c7438c2402288..4675cfe99b8f9b569c0c6087ec83c7a72a6d9436 100644 (file)
@@ -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;
        }
index d0caabe5da19be071e177d1b7a0e772233c7fbb0..7fd91c7317d4b254f295ed810db2b0eac4eec06b 100644 (file)
@@ -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;
        }
index 95c4859e94cec06692eda6b53d61135e822f143c..8d2d53b0286d1b36abf618be1cace8958d7b780a 100644 (file)
@@ -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 (file)
index 0000000..b541008
--- /dev/null
@@ -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"