Skip to content

Commit 8f9e080

Browse files
committed
Resolve relative types at compile time when possible
1 parent 47db81c commit 8f9e080

11 files changed

+205
-26
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
@@ -1354,10 +1354,10 @@ static zend_string *add_intersection_type(zend_string *str,
13541354
ZEND_TYPE_LIST_FOREACH(intersection_type_list, single_type) {
13551355
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*single_type));
13561356
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*single_type));
1357-
zend_string *name = ZEND_TYPE_NAME(*single_type);
1358-
zend_string *resolved = resolve_class_name(name, scope);
1359-
intersection_str = add_type_string(intersection_str, resolved, /* is_intersection */ true);
1360-
zend_string_release(resolved);
1357+
ZEND_ASSERT(!ZEND_TYPE_IS_RELATIVE_SELF(*single_type) && "Compile time disallowed to have 'self' in intersection");
1358+
ZEND_ASSERT(!ZEND_TYPE_IS_RELATIVE_PARENT(*single_type) && "Compile time disallowed to have 'parent' in intersection");
1359+
1360+
intersection_str = add_type_string(intersection_str, ZEND_TYPE_NAME(*single_type), /* is_intersection */ true);
13611361
} ZEND_TYPE_LIST_FOREACH_END();
13621362

13631363
ZEND_ASSERT(intersection_str);
@@ -1389,13 +1389,30 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
13891389
}
13901390
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type));
13911391
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*list_type));
1392-
zend_string *name = ZEND_TYPE_NAME(*list_type);
1393-
zend_string *resolved = resolve_class_name(name, scope);
1394-
str = add_type_string(str, resolved, /* is_intersection */ false);
1395-
zend_string_release(resolved);
1392+
1393+
/* We already have resolved types from compile time
1394+
* Mimic unresolved types for BC with "self" and "parent" */
1395+
if (!scope && ZEND_TYPE_IS_RELATIVE_SELF(*list_type)) {
1396+
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_SELF), /* is_intersection */ false);
1397+
} else if (!scope && ZEND_TYPE_IS_RELATIVE_PARENT(*list_type)) {
1398+
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_PARENT), /* is_intersection */ false);
1399+
} else {
1400+
zend_string *name = ZEND_TYPE_NAME(*list_type);
1401+
zend_string *resolved = resolve_class_name(name, scope);
1402+
str = add_type_string(str, resolved, /* is_intersection */ false);
1403+
zend_string_release(resolved);
1404+
}
13961405
} ZEND_TYPE_LIST_FOREACH_END();
13971406
} else if (ZEND_TYPE_HAS_NAME(type)) {
1398-
str = resolve_class_name(ZEND_TYPE_NAME(type), scope);
1407+
/* We already have resolved types from compile time
1408+
* Mimic unresolved types for BC with "self" and "parent" */
1409+
if (!scope && ZEND_TYPE_IS_RELATIVE_SELF(type)) {
1410+
str = ZSTR_KNOWN(ZEND_STR_SELF);
1411+
} else if (!scope && ZEND_TYPE_IS_RELATIVE_PARENT(type)) {
1412+
str = ZSTR_KNOWN(ZEND_STR_PARENT);
1413+
} else {
1414+
str = resolve_class_name(ZEND_TYPE_NAME(type), scope);
1415+
}
13991416
}
14001417

14011418
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
@@ -6592,14 +6609,14 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
65926609

65936610
return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, 0, 0);
65946611
} else {
6595-
zend_string *class_name = zend_ast_get_str(ast);
6596-
uint8_t type_code = zend_lookup_builtin_type_by_name(class_name);
6612+
zend_string *type_name = zend_ast_get_str(ast);
6613+
uint8_t type_code = zend_lookup_builtin_type_by_name(type_name);
65976614

65986615
if (type_code != 0) {
65996616
if ((ast->attr & ZEND_NAME_NOT_FQ) != ZEND_NAME_NOT_FQ) {
66006617
zend_error_noreturn(E_COMPILE_ERROR,
66016618
"Type declaration '%s' must be unqualified",
6602-
ZSTR_VAL(zend_string_tolower(class_name)));
6619+
ZSTR_VAL(zend_string_tolower(type_name)));
66036620
}
66046621

66056622
/* Transform iterable into a type union alias */
@@ -6613,38 +6630,58 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
66136630
return (zend_type) ZEND_TYPE_INIT_CODE(type_code, 0, 0);
66146631
} else {
66156632
const char *correct_name;
6616-
zend_string *orig_name = zend_ast_get_str(ast);
66176633
uint32_t fetch_type = zend_get_class_fetch_type_ast(ast);
6634+
uint32_t type_flags = 0;
6635+
zend_string *class_name = type_name;
6636+
66186637
if (fetch_type == ZEND_FETCH_CLASS_DEFAULT) {
66196638
class_name = zend_resolve_class_name_ast(ast);
66206639
zend_assert_valid_class_name(class_name);
66216640
} else {
6641+
ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_SELF || fetch_type == ZEND_FETCH_CLASS_PARENT);
6642+
66226643
zend_ensure_valid_class_fetch_type(fetch_type);
6644+
if (fetch_type == ZEND_FETCH_CLASS_SELF) {
6645+
type_flags = _ZEND_TYPE_SELF_BIT;
6646+
/* Scope might be unknown for unbound closures and traits */
6647+
if (zend_is_scope_known()) {
6648+
class_name = CG(active_class_entry)->name;
6649+
ZEND_ASSERT(class_name && "must know class name when resolving self type at compile time");
6650+
}
6651+
} else {
6652+
ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_PARENT);
6653+
type_flags = _ZEND_TYPE_PARENT_BIT;
6654+
/* Scope might be unknown for unbound closures and traits */
6655+
if (zend_is_scope_known()) {
6656+
class_name = CG(active_class_entry)->parent_name;
6657+
ZEND_ASSERT(class_name && "must know class name when resolving parent type at compile time");
6658+
}
6659+
}
66236660
zend_string_addref(class_name);
66246661
}
66256662

66266663
if (ast->attr == ZEND_NAME_NOT_FQ
6627-
&& zend_is_confusable_type(orig_name, &correct_name)
6628-
&& zend_is_not_imported(orig_name)) {
6664+
&& zend_is_confusable_type(type_name, &correct_name)
6665+
&& zend_is_not_imported(type_name)) {
66296666
const char *extra =
66306667
FC(current_namespace) ? " or import the class with \"use\"" : "";
66316668
if (correct_name) {
66326669
zend_error(E_COMPILE_WARNING,
66336670
"\"%s\" will be interpreted as a class name. Did you mean \"%s\"? "
66346671
"Write \"\\%s\"%s to suppress this warning",
6635-
ZSTR_VAL(orig_name), correct_name, ZSTR_VAL(class_name), extra);
6672+
ZSTR_VAL(type_name), correct_name, ZSTR_VAL(class_name), extra);
66366673
} else {
66376674
zend_error(E_COMPILE_WARNING,
66386675
"\"%s\" is not a supported builtin type "
66396676
"and will be interpreted as a class name. "
66406677
"Write \"\\%s\"%s to suppress this warning",
6641-
ZSTR_VAL(orig_name), ZSTR_VAL(class_name), extra);
6678+
ZSTR_VAL(type_name), ZSTR_VAL(class_name), extra);
66426679
}
66436680
}
66446681

66456682
class_name = zend_new_interned_string(class_name);
66466683
zend_alloc_ce_cache(class_name);
6647-
return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, 0, 0);
6684+
return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, type_flags);
66486685
}
66496686
}
66506687
}
@@ -6726,7 +6763,34 @@ static void zend_is_type_list_redundant_by_single_type(zend_type_list *type_list
67266763
}
67276764
if (zend_string_equals_ci(ZEND_TYPE_NAME(type_list->types[i]), ZEND_TYPE_NAME(type))) {
67286765
zend_string *single_type_str = zend_type_to_string(type);
6729-
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate type %s is redundant", ZSTR_VAL(single_type_str));
6766+
if (
6767+
ZEND_TYPE_IS_RELATIVE_SELF(type)
6768+
|| ZEND_TYPE_IS_RELATIVE_PARENT(type)
6769+
) {
6770+
if ( (
6771+
ZEND_TYPE_FULL_MASK(type)
6772+
& ZEND_TYPE_FULL_MASK(type_list->types[i])
6773+
& (_ZEND_TYPE_SELF_BIT|_ZEND_TYPE_PARENT_BIT)) != 0
6774+
) {
6775+
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate type %s is redundant", ZSTR_VAL(single_type_str));
6776+
}
6777+
/* zend_type_to_string() will return "self" or "parent" where the resolved type is stored in
6778+
* ZEND_TYPE_NAME() */
6779+
zend_error_noreturn(E_COMPILE_ERROR, "%s resolves to %s which is redundant",
6780+
ZSTR_VAL(single_type_str), ZSTR_VAL(ZEND_TYPE_NAME(type))
6781+
);
6782+
} else if (
6783+
ZEND_TYPE_IS_RELATIVE_SELF(type_list->types[i])
6784+
|| ZEND_TYPE_IS_RELATIVE_PARENT(type_list->types[i])
6785+
) {
6786+
/* zend_type_to_string() will return "self" or "parent" where the resolved type is stored in
6787+
* ZEND_TYPE_NAME() */
6788+
zend_error_noreturn(E_COMPILE_ERROR, "%s resolves to %s which is redundant",
6789+
ZEND_TYPE_IS_RELATIVE_SELF(type_list->types[i]) ? "self" : "parent", ZSTR_VAL(ZEND_TYPE_NAME(type))
6790+
);
6791+
} else {
6792+
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate type %s is redundant", ZSTR_VAL(single_type_str));
6793+
}
67306794
}
67316795
}
67326796
}
@@ -6767,6 +6831,7 @@ static zend_type zend_compile_typename_ex(
67676831
/* Switch from single name to name list. */
67686832
type_list->num_types = 1;
67696833
type_list->types[0] = type;
6834+
/* Clear MAY_BE_* type flags */
67706835
ZEND_TYPE_FULL_MASK(type_list->types[0]) &= ~_ZEND_TYPE_MAY_BE_MASK;
67716836
}
67726837
/* Mark type as list type */
@@ -6813,20 +6878,26 @@ static zend_type zend_compile_typename_ex(
68136878
"Type contains both true and false, bool should be used instead");
68146879
}
68156880
ZEND_TYPE_FULL_MASK(type) |= ZEND_TYPE_PURE_MASK(single_type);
6881+
/* Clear MAY_BE_* type flags */
68166882
ZEND_TYPE_FULL_MASK(single_type) &= ~_ZEND_TYPE_MAY_BE_MASK;
68176883

68186884
if (ZEND_TYPE_IS_COMPLEX(single_type)) {
68196885
if (!ZEND_TYPE_IS_COMPLEX(type) && !is_composite) {
68206886
/* The first class type can be stored directly as the type ptr payload. */
68216887
ZEND_TYPE_SET_PTR(type, ZEND_TYPE_NAME(single_type));
68226888
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_NAME_BIT;
6889+
/* Add flags indicating the named type is self/parent */
6890+
ZEND_TYPE_FULL_MASK(type) |= (ZEND_TYPE_FULL_MASK(single_type) & _ZEND_TYPE_RELATIVE_TYPE_MASK);
68236891
} else {
68246892
if (type_list->num_types == 0) {
68256893
/* Switch from single name to name list. */
68266894
type_list->num_types = 1;
68276895
type_list->types[0] = type;
6896+
/* Clear MAY_BE_* type flags */
68286897
ZEND_TYPE_FULL_MASK(type_list->types[0]) &= ~_ZEND_TYPE_MAY_BE_MASK;
68296898
ZEND_TYPE_SET_LIST(type, type_list);
6899+
/* Clear flags indicating the named type is self/parent */
6900+
ZEND_TYPE_FULL_MASK(type) &= ~_ZEND_TYPE_RELATIVE_TYPE_MASK;
68306901
}
68316902

68326903
type_list->types[type_list->num_types++] = single_type;
@@ -6888,10 +6959,11 @@ static zend_type zend_compile_typename_ex(
68886959
zend_string_release_ex(standard_type_str, false);
68896960
}
68906961
/* Check for "self" and "parent" too */
6891-
if (zend_string_equals_literal_ci(ZEND_TYPE_NAME(single_type), "self")
6892-
|| zend_string_equals_literal_ci(ZEND_TYPE_NAME(single_type), "parent")) {
6893-
zend_error_noreturn(E_COMPILE_ERROR,
6894-
"Type %s cannot be part of an intersection type", ZSTR_VAL(ZEND_TYPE_NAME(single_type)));
6962+
if (ZEND_TYPE_IS_RELATIVE_SELF(single_type)) {
6963+
zend_error_noreturn(E_COMPILE_ERROR, "Type self cannot be part of an intersection type");
6964+
}
6965+
if (ZEND_TYPE_IS_RELATIVE_PARENT(single_type)) {
6966+
zend_error_noreturn(E_COMPILE_ERROR, "Type parent cannot be part of an intersection type");
68956967
}
68966968

68976969
/* Add type to the type list */

Zend/zend_string.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,8 @@ EMPTY_SWITCH_DEFAULT_CASE()
621621
_(ZEND_STR_NULL_LOWERCASE, "null") \
622622
_(ZEND_STR_MIXED, "mixed") \
623623
_(ZEND_STR_TRAVERSABLE, "Traversable") \
624+
_(ZEND_STR_SELF, "self") \
625+
_(ZEND_STR_PARENT, "parent") \
624626
_(ZEND_STR_SLEEP, "__sleep") \
625627
_(ZEND_STR_WAKEUP, "__wakeup") \
626628
_(ZEND_STR_CASES, "cases") \

Zend/zend_types.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,12 @@ typedef struct {
142142
zend_type types[1];
143143
} zend_type_list;
144144

145-
#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 25
146-
#define _ZEND_TYPE_MASK ((1u << 25) - 1)
145+
#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 27
146+
#define _ZEND_TYPE_MASK ((1u << 27) - 1)
147+
/* Only one of these bits may be set. */
148+
#define _ZEND_TYPE_PARENT_BIT (1u << 26)
149+
#define _ZEND_TYPE_SELF_BIT (1u << 25)
150+
#define _ZEND_TYPE_RELATIVE_TYPE_MASK (_ZEND_TYPE_SELF_BIT|_ZEND_TYPE_PARENT_BIT)
147151
/* Only one of these bits may be set. */
148152
#define _ZEND_TYPE_NAME_BIT (1u << 24)
149153
// Used to signify that type.ptr is not a `zend_string*` but a `const char*`,
@@ -166,6 +170,14 @@ typedef struct {
166170
#define ZEND_TYPE_IS_SET(t) \
167171
(((t).type_mask & _ZEND_TYPE_MASK) != 0)
168172

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

0 commit comments

Comments
 (0)