Skip to content

Commit eb3afff

Browse files
committed
Resolve relative types at compile time when possible
1 parent abbb2b9 commit eb3afff

11 files changed

+206
-27
lines changed

Zend/tests/magic_methods_021.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Foo2 {
1212
}
1313

1414
class Foo3 {
15-
public static function __set_state(array $data): Foo3|self {}
15+
public static function __set_state(array $data): Foo3|Foo2 {}
1616
}
1717

1818
?>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Duplicate parent type
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public function method(array $data) {}
8+
}
9+
class Bar extends Foo {
10+
public function method(array $data): parent|parent {}
11+
}
12+
13+
?>
14+
--EXPECTF--
15+
Fatal error: Duplicate type parent is redundant in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Duplicate self type
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public function method(array $data): self|self {}
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Duplicate type self is redundant in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Duplicate static type
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public function method(array $data): static|static {}
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Duplicate type static is redundant in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Relative class type self resolving to an existing entry (after variation)
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public function method(array $data): Foo|self {}
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: self resolves to Foo which is redundant in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Relative class type self resolving to an existing entry (before variation)
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public function method(array $data): self|Foo {}
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: self resolves to Foo which is redundant in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Relative class type parent resolving to an existing entry (after variation)
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public function method(array $data) {}
8+
}
9+
class Bar extends Foo {
10+
public function method(array $data): Foo|parent {}
11+
}
12+
13+
?>
14+
--EXPECTF--
15+
Fatal error: parent resolves to Foo which is redundant in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Relative class type parent resolving to an existing entry (before variation)
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public function method(array $data) {}
8+
}
9+
class Bar extends Foo {
10+
public function method(array $data): parent|Foo {}
11+
}
12+
13+
?>
14+
--EXPECTF--
15+
Fatal error: parent resolves to Foo which is redundant in %s on line %d

Zend/zend_compile.c

Lines changed: 95 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,10 +1316,10 @@ static zend_string *add_intersection_type(zend_string *str,
13161316
ZEND_TYPE_LIST_FOREACH(intersection_type_list, single_type) {
13171317
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*single_type));
13181318
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*single_type));
1319-
zend_string *name = ZEND_TYPE_NAME(*single_type);
1320-
zend_string *resolved = resolve_class_name(name, scope);
1321-
intersection_str = add_type_string(intersection_str, resolved, /* is_intersection */ true);
1322-
zend_string_release(resolved);
1319+
ZEND_ASSERT(!ZEND_TYPE_IS_RELATIVE_SELF(*single_type) && "Compile time disallowed to have 'self' in intersection");
1320+
ZEND_ASSERT(!ZEND_TYPE_IS_RELATIVE_PARENT(*single_type) && "Compile time disallowed to have 'parent' in intersection");
1321+
1322+
intersection_str = add_type_string(intersection_str, ZEND_TYPE_NAME(*single_type), /* is_intersection */ true);
13231323
} ZEND_TYPE_LIST_FOREACH_END();
13241324

13251325
ZEND_ASSERT(intersection_str);
@@ -1351,13 +1351,30 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
13511351
}
13521352
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type));
13531353
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*list_type));
1354-
zend_string *name = ZEND_TYPE_NAME(*list_type);
1355-
zend_string *resolved = resolve_class_name(name, scope);
1356-
str = add_type_string(str, resolved, /* is_intersection */ false);
1357-
zend_string_release(resolved);
1354+
1355+
/* We already have resolved types from compile time
1356+
* Mimic unresolved types for BC with "self" and "parent" */
1357+
if (!scope && ZEND_TYPE_IS_RELATIVE_SELF(*list_type)) {
1358+
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_SELF), /* is_intersection */ false);
1359+
} else if (!scope && ZEND_TYPE_IS_RELATIVE_PARENT(*list_type)) {
1360+
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_PARENT), /* is_intersection */ false);
1361+
} else {
1362+
zend_string *name = ZEND_TYPE_NAME(*list_type);
1363+
zend_string *resolved = resolve_class_name(name, scope);
1364+
str = add_type_string(str, resolved, /* is_intersection */ false);
1365+
zend_string_release(resolved);
1366+
}
13581367
} ZEND_TYPE_LIST_FOREACH_END();
13591368
} else if (ZEND_TYPE_HAS_NAME(type)) {
1360-
str = resolve_class_name(ZEND_TYPE_NAME(type), scope);
1369+
/* We already have resolved types from compile time
1370+
* Mimic unresolved types for BC with "self" and "parent" */
1371+
if (!scope && ZEND_TYPE_IS_RELATIVE_SELF(type)) {
1372+
str = ZSTR_KNOWN(ZEND_STR_SELF);
1373+
} else if (!scope && ZEND_TYPE_IS_RELATIVE_PARENT(type)) {
1374+
str = ZSTR_KNOWN(ZEND_STR_PARENT);
1375+
} else {
1376+
str = resolve_class_name(ZEND_TYPE_NAME(type), scope);
1377+
}
13611378
}
13621379

13631380
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
@@ -6361,14 +6378,14 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
63616378

63626379
return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, 0, 0);
63636380
} else {
6364-
zend_string *class_name = zend_ast_get_str(ast);
6365-
uint8_t type_code = zend_lookup_builtin_type_by_name(class_name);
6381+
zend_string *type_name = zend_ast_get_str(ast);
6382+
uint8_t type_code = zend_lookup_builtin_type_by_name(type_name);
63666383

63676384
if (type_code != 0) {
63686385
if ((ast->attr & ZEND_NAME_NOT_FQ) != ZEND_NAME_NOT_FQ) {
63696386
zend_error_noreturn(E_COMPILE_ERROR,
63706387
"Type declaration '%s' must be unqualified",
6371-
ZSTR_VAL(zend_string_tolower(class_name)));
6388+
ZSTR_VAL(zend_string_tolower(type_name)));
63726389
}
63736390

63746391
/* Transform iterable into a type union alias */
@@ -6382,38 +6399,58 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
63826399
return (zend_type) ZEND_TYPE_INIT_CODE(type_code, 0, 0);
63836400
} else {
63846401
const char *correct_name;
6385-
zend_string *orig_name = zend_ast_get_str(ast);
63866402
uint32_t fetch_type = zend_get_class_fetch_type_ast(ast);
6403+
uint32_t type_flags = 0;
6404+
zend_string *class_name = type_name;
6405+
63876406
if (fetch_type == ZEND_FETCH_CLASS_DEFAULT) {
63886407
class_name = zend_resolve_class_name_ast(ast);
63896408
zend_assert_valid_class_name(class_name);
63906409
} else {
6410+
ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_SELF || fetch_type == ZEND_FETCH_CLASS_PARENT);
6411+
63916412
zend_ensure_valid_class_fetch_type(fetch_type);
6413+
if (fetch_type == ZEND_FETCH_CLASS_SELF) {
6414+
type_flags = _ZEND_TYPE_SELF_BIT;
6415+
/* Scope might be unknown for unbound closures and traits */
6416+
if (zend_is_scope_known()) {
6417+
class_name = CG(active_class_entry)->name;
6418+
ZEND_ASSERT(class_name && "must know class name when resolving self type at compile time");
6419+
}
6420+
} else {
6421+
ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_PARENT);
6422+
type_flags = _ZEND_TYPE_PARENT_BIT;
6423+
/* Scope might be unknown for unbound closures and traits */
6424+
if (zend_is_scope_known()) {
6425+
class_name = CG(active_class_entry)->parent_name;
6426+
ZEND_ASSERT(class_name && "must know class name when resolving parent type at compile time");
6427+
}
6428+
}
63926429
zend_string_addref(class_name);
63936430
}
63946431

63956432
if (ast->attr == ZEND_NAME_NOT_FQ
6396-
&& zend_is_confusable_type(orig_name, &correct_name)
6397-
&& zend_is_not_imported(orig_name)) {
6433+
&& zend_is_confusable_type(type_name, &correct_name)
6434+
&& zend_is_not_imported(type_name)) {
63986435
const char *extra =
63996436
FC(current_namespace) ? " or import the class with \"use\"" : "";
64006437
if (correct_name) {
64016438
zend_error(E_COMPILE_WARNING,
64026439
"\"%s\" will be interpreted as a class name. Did you mean \"%s\"? "
64036440
"Write \"\\%s\"%s to suppress this warning",
6404-
ZSTR_VAL(orig_name), correct_name, ZSTR_VAL(class_name), extra);
6441+
ZSTR_VAL(type_name), correct_name, ZSTR_VAL(class_name), extra);
64056442
} else {
64066443
zend_error(E_COMPILE_WARNING,
64076444
"\"%s\" is not a supported builtin type "
64086445
"and will be interpreted as a class name. "
64096446
"Write \"\\%s\"%s to suppress this warning",
6410-
ZSTR_VAL(orig_name), ZSTR_VAL(class_name), extra);
6447+
ZSTR_VAL(type_name), ZSTR_VAL(class_name), extra);
64116448
}
64126449
}
64136450

64146451
class_name = zend_new_interned_string(class_name);
64156452
zend_alloc_ce_cache(class_name);
6416-
return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, 0, 0);
6453+
return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, type_flags);
64176454
}
64186455
}
64196456
}
@@ -6495,7 +6532,34 @@ static void zend_is_type_list_redundant_by_single_type(zend_type_list *type_list
64956532
}
64966533
if (zend_string_equals_ci(ZEND_TYPE_NAME(type_list->types[i]), ZEND_TYPE_NAME(type))) {
64976534
zend_string *single_type_str = zend_type_to_string(type);
6498-
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate type %s is redundant", ZSTR_VAL(single_type_str));
6535+
if (
6536+
ZEND_TYPE_IS_RELATIVE_SELF(type)
6537+
|| ZEND_TYPE_IS_RELATIVE_PARENT(type)
6538+
) {
6539+
if ( (
6540+
ZEND_TYPE_FULL_MASK(type)
6541+
& ZEND_TYPE_FULL_MASK(type_list->types[i])
6542+
& (_ZEND_TYPE_SELF_BIT|_ZEND_TYPE_PARENT_BIT)) != 0
6543+
) {
6544+
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate type %s is redundant", ZSTR_VAL(single_type_str));
6545+
}
6546+
/* zend_type_to_string() will return "self" or "parent" where the resolved type is stored in
6547+
* ZEND_TYPE_NAME() */
6548+
zend_error_noreturn(E_COMPILE_ERROR, "%s resolves to %s which is redundant",
6549+
ZSTR_VAL(single_type_str), ZSTR_VAL(ZEND_TYPE_NAME(type))
6550+
);
6551+
} else if (
6552+
ZEND_TYPE_IS_RELATIVE_SELF(type_list->types[i])
6553+
|| ZEND_TYPE_IS_RELATIVE_PARENT(type_list->types[i])
6554+
) {
6555+
/* zend_type_to_string() will return "self" or "parent" where the resolved type is stored in
6556+
* ZEND_TYPE_NAME() */
6557+
zend_error_noreturn(E_COMPILE_ERROR, "%s resolves to %s which is redundant",
6558+
ZEND_TYPE_IS_RELATIVE_SELF(type_list->types[i]) ? "self" : "parent", ZSTR_VAL(ZEND_TYPE_NAME(type))
6559+
);
6560+
} else {
6561+
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate type %s is redundant", ZSTR_VAL(single_type_str));
6562+
}
64996563
}
65006564
}
65016565
}
@@ -6534,6 +6598,7 @@ static zend_type zend_compile_typename(
65346598
/* Switch from single name to name list. */
65356599
type_list->num_types = 1;
65366600
type_list->types[0] = type;
6601+
/* Clear MAY_BE_* type flags */
65376602
ZEND_TYPE_FULL_MASK(type_list->types[0]) &= ~_ZEND_TYPE_MAY_BE_MASK;
65386603
}
65396604
/* Mark type as list type */
@@ -6580,20 +6645,26 @@ static zend_type zend_compile_typename(
65806645
"Type contains both true and false, bool should be used instead");
65816646
}
65826647
ZEND_TYPE_FULL_MASK(type) |= ZEND_TYPE_PURE_MASK(single_type);
6648+
/* Clear MAY_BE_* type flags */
65836649
ZEND_TYPE_FULL_MASK(single_type) &= ~_ZEND_TYPE_MAY_BE_MASK;
65846650

65856651
if (ZEND_TYPE_IS_COMPLEX(single_type)) {
65866652
if (!ZEND_TYPE_IS_COMPLEX(type) && !is_composite) {
65876653
/* The first class type can be stored directly as the type ptr payload. */
65886654
ZEND_TYPE_SET_PTR(type, ZEND_TYPE_NAME(single_type));
65896655
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_NAME_BIT;
6656+
/* Add flags indicating the named type is self/parent */
6657+
ZEND_TYPE_FULL_MASK(type) |= (ZEND_TYPE_FULL_MASK(single_type) & _ZEND_TYPE_RELATIVE_TYPE_MASK);
65906658
} else {
65916659
if (type_list->num_types == 0) {
65926660
/* Switch from single name to name list. */
65936661
type_list->num_types = 1;
65946662
type_list->types[0] = type;
6663+
/* Clear MAY_BE_* type flags */
65956664
ZEND_TYPE_FULL_MASK(type_list->types[0]) &= ~_ZEND_TYPE_MAY_BE_MASK;
65966665
ZEND_TYPE_SET_LIST(type, type_list);
6666+
/* Clear flags indicating the named type is self/parent */
6667+
ZEND_TYPE_FULL_MASK(type) &= ~_ZEND_TYPE_RELATIVE_TYPE_MASK;
65976668
}
65986669

65996670
type_list->types[type_list->num_types++] = single_type;
@@ -6655,10 +6726,11 @@ static zend_type zend_compile_typename(
66556726
zend_string_release_ex(standard_type_str, false);
66566727
}
66576728
/* Check for "self" and "parent" too */
6658-
if (zend_string_equals_literal_ci(ZEND_TYPE_NAME(single_type), "self")
6659-
|| zend_string_equals_literal_ci(ZEND_TYPE_NAME(single_type), "parent")) {
6660-
zend_error_noreturn(E_COMPILE_ERROR,
6661-
"Type %s cannot be part of an intersection type", ZSTR_VAL(ZEND_TYPE_NAME(single_type)));
6729+
if (ZEND_TYPE_IS_RELATIVE_SELF(single_type)) {
6730+
zend_error_noreturn(E_COMPILE_ERROR, "Type self cannot be part of an intersection type");
6731+
}
6732+
if (ZEND_TYPE_IS_RELATIVE_PARENT(single_type)) {
6733+
zend_error_noreturn(E_COMPILE_ERROR, "Type parent cannot be part of an intersection type");
66626734
}
66636735

66646736
/* Add type to the type list */

Zend/zend_string.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,8 @@ EMPTY_SWITCH_DEFAULT_CASE()
619619
_(ZEND_STR_NULL_LOWERCASE, "null") \
620620
_(ZEND_STR_MIXED, "mixed") \
621621
_(ZEND_STR_TRAVERSABLE, "Traversable") \
622+
_(ZEND_STR_SELF, "self") \
623+
_(ZEND_STR_PARENT, "parent") \
622624
_(ZEND_STR_SLEEP, "__sleep") \
623625
_(ZEND_STR_WAKEUP, "__wakeup") \
624626
_(ZEND_STR_CASES, "cases") \

Zend/zend_types.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,14 @@ typedef struct {
141141
zend_type types[1];
142142
} zend_type_list;
143143

144-
#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 25
145-
#define _ZEND_TYPE_MASK ((1u << 25) - 1)
144+
#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 26
145+
#define _ZEND_TYPE_MASK ((1u << 26) - 1)
146146
/* Only one of these bits may be set. */
147-
#define _ZEND_TYPE_NAME_BIT (1u << 24)
147+
#define _ZEND_TYPE_PARENT_BIT (1u << 25)
148+
#define _ZEND_TYPE_SELF_BIT (1u << 24)
149+
#define _ZEND_TYPE_RELATIVE_TYPE_MASK (_ZEND_TYPE_SELF_BIT|_ZEND_TYPE_PARENT_BIT)
150+
/* Only one of these bits may be set. */
151+
#define _ZEND_TYPE_NAME_BIT (1u << 23)
148152
#define _ZEND_TYPE_LIST_BIT (1u << 22)
149153
#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT)
150154
/* For BC behaviour with iterable type */
@@ -163,6 +167,14 @@ typedef struct {
163167
#define ZEND_TYPE_IS_SET(t) \
164168
(((t).type_mask & _ZEND_TYPE_MASK) != 0)
165169

170+
171+
/* To determine if the type resolved type was written with "self" */
172+
#define ZEND_TYPE_IS_RELATIVE_SELF(t) \
173+
((((t).type_mask) & _ZEND_TYPE_SELF_BIT) != 0)
174+
/* To determine if the type resolved type was written with "parent" */
175+
#define ZEND_TYPE_IS_RELATIVE_PARENT(t) \
176+
((((t).type_mask) & _ZEND_TYPE_PARENT_BIT) != 0)
177+
166178
/* If a type is complex it means it's either a list with a union or intersection,
167179
* or the void pointer is a class name */
168180
#define ZEND_TYPE_IS_COMPLEX(t) \

0 commit comments

Comments
 (0)