1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Zend: Resolve self and parent types at compile time (#17755)

This does not apply to traits.
This commit is contained in:
Gina Peter Banyard
2025-02-11 15:15:32 +00:00
committed by GitHub
parent 15d7b83ad3
commit 1ad7743133
20 changed files with 230 additions and 29 deletions

View File

@@ -12,7 +12,7 @@ class Foo2 {
}
class Foo3 {
public static function __set_state(array $data): Foo3|self {}
public static function __set_state(array $data): Foo3|Foo2 {}
}
?>

View File

@@ -0,0 +1,15 @@
--TEST--
parent type can take part in an intersection type is resolvable at compile time
--FILE--
<?php
class A {}
class B extends A {
public function foo(): parent&Iterator {}
}
?>
DONE
--EXPECT--
DONE

View File

@@ -1,14 +1,13 @@
--TEST--
parent type cannot take part in an intersection type
parent type cannot take part in an intersection type if not resolvable at compile time
--FILE--
<?php
class A {}
class B extends A {
trait T {
public function foo(): parent&Iterator {}
}
?>
DONE
--EXPECTF--
Fatal error: Type parent cannot be part of an intersection type in %s on line %d

View File

@@ -0,0 +1,13 @@
--TEST--
parent type cannot take part in an intersection type if not resolvable at compile time
--FILE--
<?php
trait T {
public function foo(): PARENT&Iterator {}
}
?>
DONE
--EXPECTF--
Fatal error: Type PARENT cannot be part of an intersection type in %s on line %d

View File

@@ -0,0 +1,13 @@
--TEST--
self type can take part in an intersection type is resolvable at compile time
--FILE--
<?php
class A {
public function foo(): self&Iterator {}
}
?>
DONE
--EXPECT--
DONE

View File

@@ -1,12 +1,13 @@
--TEST--
self type cannot take part in an intersection type
self type cannot take part in an intersection type if not resolvable at compile time
--FILE--
<?php
class A {
trait T {
public function foo(): self&Iterator {}
}
?>
DONE
--EXPECTF--
Fatal error: Type self cannot be part of an intersection type in %s on line %d

View File

@@ -0,0 +1,13 @@
--TEST--
self type cannot take part in an intersection type if not resolvable at compile time
--FILE--
<?php
trait T {
public function foo(): SELF&Iterator {}
}
?>
DONE
--EXPECTF--
Fatal error: Type SELF cannot be part of an intersection type in %s on line %d

View File

@@ -0,0 +1,15 @@
--TEST--
Duplicate parent type in different cases
--FILE--
<?php
class Foo {
public function method(array $data) {}
}
class Bar extends Foo {
public function method(array $data): parent|PARENT {}
}
?>
--EXPECTF--
Fatal error: Duplicate type Foo is redundant in %s on line %d

View File

@@ -0,0 +1,12 @@
--TEST--
Duplicate self type in different cases
--FILE--
<?php
class Foo {
public function method(array $data): self|SELF {}
}
?>
--EXPECTF--
Fatal error: Duplicate type Foo is redundant in %s on line %d

View File

@@ -0,0 +1,15 @@
--TEST--
Duplicate parent type
--FILE--
<?php
class Foo {
public function method(array $data) {}
}
class Bar extends Foo {
public function method(array $data): parent|parent {}
}
?>
--EXPECTF--
Fatal error: Duplicate type Foo is redundant in %s on line %d

View File

@@ -0,0 +1,12 @@
--TEST--
Duplicate self type
--FILE--
<?php
class Foo {
public function method(array $data): self|self {}
}
?>
--EXPECTF--
Fatal error: Duplicate type Foo is redundant in %s on line %d

View File

@@ -0,0 +1,12 @@
--TEST--
Duplicate static type
--FILE--
<?php
class Foo {
public function method(array $data): static|static {}
}
?>
--EXPECTF--
Fatal error: Duplicate type static is redundant in %s on line %d

View File

@@ -0,0 +1,13 @@
--TEST--
Relative class type self resolving to an existing entry (after variation)
--FILE--
<?php
class Foo {
public function method(array $data): Foo|self {}
}
?>
DONE
--EXPECTF--
Fatal error: Duplicate type Foo is redundant in %s on line %d

View File

@@ -0,0 +1,13 @@
--TEST--
Relative class type self resolving to an existing entry (before variation)
--FILE--
<?php
class Foo {
public function method(array $data): self|Foo {}
}
?>
DONE
--EXPECTF--
Fatal error: Duplicate type Foo is redundant in %s on line %d

View File

@@ -0,0 +1,16 @@
--TEST--
Relative class type parent resolving to an existing entry (after variation)
--FILE--
<?php
class Foo {
public function method(array $data) {}
}
class Bar extends Foo {
public function method(array $data): Foo|parent {}
}
?>
DONE
--EXPECTF--
Fatal error: Duplicate type Foo is redundant in %s on line %d

View File

@@ -0,0 +1,16 @@
--TEST--
Relative class type parent resolving to an existing entry (before variation)
--FILE--
<?php
class Foo {
public function method(array $data) {}
}
class Bar extends Foo {
public function method(array $data): parent|Foo {}
}
?>
DONE
--EXPECTF--
Fatal error: Duplicate type Foo is redundant in %s on line %d

View File

@@ -1409,10 +1409,8 @@ static zend_string *add_intersection_type(zend_string *str,
ZEND_TYPE_LIST_FOREACH(intersection_type_list, single_type) {
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*single_type));
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*single_type));
zend_string *name = ZEND_TYPE_NAME(*single_type);
zend_string *resolved = resolve_class_name(name, scope);
intersection_str = add_type_string(intersection_str, resolved, /* is_intersection */ true);
zend_string_release(resolved);
intersection_str = add_type_string(intersection_str, ZEND_TYPE_NAME(*single_type), /* is_intersection */ true);
} ZEND_TYPE_LIST_FOREACH_END();
ZEND_ASSERT(intersection_str);
@@ -1444,6 +1442,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
}
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type));
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*list_type));
zend_string *name = ZEND_TYPE_NAME(*list_type);
zend_string *resolved = resolve_class_name(name, scope);
str = add_type_string(str, resolved, /* is_intersection */ false);
@@ -6957,14 +6956,14 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, 0, 0);
} else {
zend_string *class_name = zend_ast_get_str(ast);
uint8_t type_code = zend_lookup_builtin_type_by_name(class_name);
zend_string *type_name = zend_ast_get_str(ast);
uint8_t type_code = zend_lookup_builtin_type_by_name(type_name);
if (type_code != 0) {
if ((ast->attr & ZEND_NAME_NOT_FQ) != ZEND_NAME_NOT_FQ) {
zend_error_noreturn(E_COMPILE_ERROR,
"Type declaration '%s' must be unqualified",
ZSTR_VAL(zend_string_tolower(class_name)));
ZSTR_VAL(zend_string_tolower(type_name)));
}
/* Transform iterable into a type union alias */
@@ -6978,38 +6977,55 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
return (zend_type) ZEND_TYPE_INIT_CODE(type_code, 0, 0);
} else {
const char *correct_name;
zend_string *orig_name = zend_ast_get_str(ast);
uint32_t fetch_type = zend_get_class_fetch_type_ast(ast);
zend_string *class_name = type_name;
if (fetch_type == ZEND_FETCH_CLASS_DEFAULT) {
class_name = zend_resolve_class_name_ast(ast);
zend_assert_valid_class_name(class_name, "a type name");
} else {
ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_SELF || fetch_type == ZEND_FETCH_CLASS_PARENT);
zend_ensure_valid_class_fetch_type(fetch_type);
if (fetch_type == ZEND_FETCH_CLASS_SELF) {
/* Scope might be unknown for unbound closures and traits */
if (zend_is_scope_known()) {
class_name = CG(active_class_entry)->name;
ZEND_ASSERT(class_name && "must know class name when resolving self type at compile time");
}
} else {
ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_PARENT);
/* Scope might be unknown for unbound closures and traits */
if (zend_is_scope_known()) {
class_name = CG(active_class_entry)->parent_name;
ZEND_ASSERT(class_name && "must know class name when resolving parent type at compile time");
}
}
zend_string_addref(class_name);
}
if (ast->attr == ZEND_NAME_NOT_FQ
&& zend_is_confusable_type(orig_name, &correct_name)
&& zend_is_not_imported(orig_name)) {
&& zend_is_confusable_type(type_name, &correct_name)
&& zend_is_not_imported(type_name)) {
const char *extra =
FC(current_namespace) ? " or import the class with \"use\"" : "";
if (correct_name) {
zend_error(E_COMPILE_WARNING,
"\"%s\" will be interpreted as a class name. Did you mean \"%s\"? "
"Write \"\\%s\"%s to suppress this warning",
ZSTR_VAL(orig_name), correct_name, ZSTR_VAL(class_name), extra);
ZSTR_VAL(type_name), correct_name, ZSTR_VAL(class_name), extra);
} else {
zend_error(E_COMPILE_WARNING,
"\"%s\" is not a supported builtin type "
"and will be interpreted as a class name. "
"Write \"\\%s\"%s to suppress this warning",
ZSTR_VAL(orig_name), ZSTR_VAL(class_name), extra);
ZSTR_VAL(type_name), ZSTR_VAL(class_name), extra);
}
}
class_name = zend_new_interned_string(class_name);
zend_alloc_ce_cache(class_name);
return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, 0, 0);
return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, 0);
}
}
}
@@ -7132,6 +7148,7 @@ static zend_type zend_compile_typename_ex(
/* Switch from single name to name list. */
type_list->num_types = 1;
type_list->types[0] = type;
/* Clear MAY_BE_* type flags */
ZEND_TYPE_FULL_MASK(type_list->types[0]) &= ~_ZEND_TYPE_MAY_BE_MASK;
}
/* Mark type as list type */
@@ -7178,6 +7195,7 @@ static zend_type zend_compile_typename_ex(
"Type contains both true and false, bool must be used instead");
}
ZEND_TYPE_FULL_MASK(type) |= ZEND_TYPE_PURE_MASK(single_type);
/* Clear MAY_BE_* type flags */
ZEND_TYPE_FULL_MASK(single_type) &= ~_ZEND_TYPE_MAY_BE_MASK;
if (ZEND_TYPE_IS_COMPLEX(single_type)) {
@@ -7190,6 +7208,7 @@ static zend_type zend_compile_typename_ex(
/* Switch from single name to name list. */
type_list->num_types = 1;
type_list->types[0] = type;
/* Clear MAY_BE_* type flags */
ZEND_TYPE_FULL_MASK(type_list->types[0]) &= ~_ZEND_TYPE_MAY_BE_MASK;
ZEND_TYPE_SET_LIST(type, type_list);
}
@@ -7253,8 +7272,10 @@ static zend_type zend_compile_typename_ex(
zend_string_release_ex(standard_type_str, false);
}
/* Check for "self" and "parent" too */
if (zend_string_equals_literal_ci(ZEND_TYPE_NAME(single_type), "self")
|| zend_string_equals_literal_ci(ZEND_TYPE_NAME(single_type), "parent")) {
if (
zend_string_equals_ci(ZEND_TYPE_NAME(single_type), ZSTR_KNOWN(ZEND_STR_SELF))
|| zend_string_equals_ci(ZEND_TYPE_NAME(single_type), ZSTR_KNOWN(ZEND_STR_PARENT))
) {
zend_error_noreturn(E_COMPILE_ERROR,
"Type %s cannot be part of an intersection type", ZSTR_VAL(ZEND_TYPE_NAME(single_type)));
}

View File

@@ -624,6 +624,8 @@ EMPTY_SWITCH_DEFAULT_CASE()
_(ZEND_STR_NULL_LOWERCASE, "null") \
_(ZEND_STR_MIXED, "mixed") \
_(ZEND_STR_TRAVERSABLE, "Traversable") \
_(ZEND_STR_SELF, "self") \
_(ZEND_STR_PARENT, "parent") \
_(ZEND_STR_SLEEP, "__sleep") \
_(ZEND_STR_WAKEUP, "__wakeup") \
_(ZEND_STR_CASES, "cases") \

View File

@@ -166,12 +166,12 @@ string(10) "SplSubject"
bool(true)
bool(false)
bool(false)
string(4) "self"
string(1) "c"
** Method 2 - parameter 0
bool(true)
bool(false)
bool(false)
string(6) "parent"
string(8) "stdClass"
** Method 3 - parameter 0
bool(true)
bool(false)
@@ -195,12 +195,12 @@ string(3) "int"
bool(true)
bool(false)
bool(false)
string(4) "self"
string(1) "c"
** Function/method return type 4
bool(true)
bool(false)
bool(false)
string(6) "parent"
string(8) "stdClass"
** Function/method return type 5
bool(true)
bool(false)

View File

@@ -48,13 +48,13 @@ foreach ((new ReflectionClass(C::class))->getMethods() as $method) {
--EXPECT--
C::a()
$method->getReturnType() returns ReflectionNamedType
$method->getReturnType()->__toString() returns self
$method->getReturnType()->__toString() returns C
C::b()
$method->getReturnType() returns ReflectionUnionType
$method->getReturnType()->__toString() returns stdClass|self
$method->getReturnType()->__toString() returns stdClass|C
$method->getReturnType()->getTypes() returns an array with 2 element(s)
type(s) in union: ReflectionNamedType(stdClass), ReflectionNamedType(self)
type(s) in union: ReflectionNamedType(stdClass), ReflectionNamedType(C)
C::c()
$method->getReturnType() returns ReflectionNamedType