Skip to content

Add static return type #5062

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

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions Zend/tests/type_declarations/static_type_in_final_class.phpt
Original file line number Diff line number Diff line change
@@ -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) {
}
10 changes: 10 additions & 0 deletions Zend/tests/type_declarations/static_type_outside_class.phpt
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions Zend/tests/type_declarations/static_type_param.phpt
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions Zend/tests/type_declarations/static_type_property.phpt
Original file line number Diff line number Diff line change
@@ -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
91 changes: 91 additions & 0 deletions Zend/tests/type_declarations/static_type_return.phpt
Original file line number Diff line number Diff line change
@@ -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) {
}
38 changes: 38 additions & 0 deletions Zend/tests/type_declarations/static_type_trait.phpt
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions Zend/tests/type_declarations/variance/static_variance_failure.phpt
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions Zend/tests/type_declarations/variance/static_variance_success.phpt
Original file line number Diff line number Diff line change
@@ -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===
16 changes: 15 additions & 1 deletion Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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",
Expand Down
41 changes: 35 additions & 6 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 */
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1074,6 +1100,9 @@ static zend_always_inline zend_bool zend_check_type_slow(
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;
Expand Down Expand Up @@ -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);
}
Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading