Skip to content

Commit c53db34

Browse files
committed
Resolve trait relative class types when added to a class
Also throw a compile error if trying to use a trait using the parent type in a class not having a parent
1 parent 409ea6c commit c53db34

12 files changed

+726
-0
lines changed
File renamed without changes.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Cannot use a trait which references parent as a type in a class with no parent, single type
3+
--FILE--
4+
<?php
5+
trait TraitExample {
6+
public function bar(): parent { return parent::class; }
7+
}
8+
9+
class A {
10+
use TraitExample;
11+
}
12+
?>
13+
--EXPECTF--
14+
Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Cannot use a trait which references parent as a type in a class with no parent, nullable type
3+
--FILE--
4+
<?php
5+
trait TraitExample {
6+
public function bar(): ?parent { return parent::class; }
7+
}
8+
9+
class A {
10+
use TraitExample;
11+
}
12+
?>
13+
--EXPECTF--
14+
Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Cannot use a trait which references parent as a type in a class with no parent, union type
3+
--FILE--
4+
<?php
5+
trait TraitExample {
6+
public function bar(): int|parent { return parent::class; }
7+
}
8+
9+
class A {
10+
use TraitExample;
11+
}
12+
?>
13+
--EXPECTF--
14+
Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Cannot use a trait which references parent as a type in a class with no parent, DNF type
3+
--FILE--
4+
<?php
5+
trait TraitExample {
6+
public function bar(): (X&Y)|parent { return parent::class; }
7+
}
8+
9+
class A {
10+
use TraitExample;
11+
}
12+
?>
13+
--EXPECTF--
14+
Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d

Zend/zend_inheritance.c

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1898,6 +1898,102 @@ static zend_class_entry *fixup_trait_scope(const zend_function *fn, zend_class_e
18981898
return fn->common.scope->ce_flags & ZEND_ACC_TRAIT ? ce : fn->common.scope;
18991899
}
19001900

1901+
/* We cannot modify the type in-place (e.g. via a pointer) as it is written to SHM */
1902+
static zend_type zend_resolve_single_type(zend_type type, const zend_class_entry *const ce)
1903+
{
1904+
/* Only built-in types + static */
1905+
if (!ZEND_TYPE_IS_COMPLEX(type)) {
1906+
return type;
1907+
}
1908+
1909+
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(type) || (ZEND_TYPE_HAS_LIST(type)));
1910+
if (ZEND_TYPE_HAS_NAME(type)) {
1911+
if (ZEND_TYPE_IS_RELATIVE_SELF(type)) {
1912+
zend_type resolved_type = (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(ce->name), /* allows_null */ false, /* extra_flags */ ZEND_TYPE_FULL_MASK(type));
1913+
return resolved_type;
1914+
} else if (ZEND_TYPE_IS_RELATIVE_PARENT(type)) {
1915+
if (!ce->parent) {
1916+
zend_error_noreturn(E_COMPILE_ERROR,
1917+
"Cannot use trait which has \"parent\" as a type when current class scope has no parent");
1918+
return (zend_type) ZEND_TYPE_INIT_NONE(0);
1919+
}
1920+
zend_type resolved_type = (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(ce->parent->name), /* allows_null */ false, /* extra_flags */ ZEND_TYPE_FULL_MASK(type));
1921+
return resolved_type;
1922+
} else {
1923+
return type;
1924+
}
1925+
}
1926+
1927+
/* Intersection types cannot have relative class_types */
1928+
if (ZEND_TYPE_IS_INTERSECTION(type)) {
1929+
return type;
1930+
}
1931+
1932+
const zend_type_list *union_type_list = ZEND_TYPE_LIST(type);
1933+
zend_type *single_type;
1934+
1935+
/* TODO Only do allocation if need to resolve types, as type is stored in SHM */
1936+
zend_type_list *new_union_type_list = zend_arena_alloc(&CG(arena), ZEND_TYPE_LIST_SIZE(union_type_list->num_types));
1937+
memcpy(new_union_type_list, union_type_list, ZEND_TYPE_LIST_SIZE(union_type_list->num_types));
1938+
1939+
ZEND_TYPE_LIST_FOREACH(new_union_type_list, single_type) {
1940+
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*single_type) || (ZEND_TYPE_IS_INTERSECTION(*single_type)));
1941+
1942+
/* Intersections types cannot have self or parent */
1943+
if (ZEND_TYPE_IS_INTERSECTION(*single_type)) {
1944+
continue;
1945+
}
1946+
if (ZEND_TYPE_IS_RELATIVE_SELF(*single_type)) {
1947+
zend_type resolved_type = (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(ce->name), /* allows_null */ false, /* extra_flags */ ZEND_TYPE_FULL_MASK(*single_type));
1948+
memmove(single_type, &resolved_type, sizeof(zend_type));
1949+
}
1950+
if (ZEND_TYPE_IS_RELATIVE_PARENT(*single_type)) {
1951+
if (!ce->parent) {
1952+
zend_error_noreturn(E_COMPILE_ERROR,
1953+
"Cannot use trait which has \"parent\" as a type when current class scope has no parent");
1954+
return (zend_type) ZEND_TYPE_INIT_NONE(0);
1955+
}
1956+
zend_type resolved_type = (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(ce->parent->name), /* allows_null */ false, /* extra_flags */ ZEND_TYPE_FULL_MASK(*single_type));
1957+
memmove(single_type, &resolved_type, sizeof(zend_type));
1958+
}
1959+
} ZEND_TYPE_LIST_FOREACH_END();
1960+
1961+
zend_type new_type = (zend_type) ZEND_TYPE_INIT_NONE(0);
1962+
ZEND_TYPE_SET_LIST(new_type, new_union_type_list);
1963+
ZEND_TYPE_FULL_MASK(new_type) |= _ZEND_TYPE_ARENA_BIT;
1964+
/* Inform that the type list is a union type */
1965+
ZEND_TYPE_FULL_MASK(new_type) |= _ZEND_TYPE_UNION_BIT;
1966+
return new_type;
1967+
}
1968+
1969+
static void zend_resolve_trait_relative_class_types(zend_function *const fn, const zend_class_entry *const ce)
1970+
{
1971+
/* We are adding trait methods to another trait, delay resolution */
1972+
if (ce->ce_flags & ZEND_ACC_TRAIT) {
1973+
return;
1974+
}
1975+
/* No type info */
1976+
if (!fn->common.arg_info) {
1977+
return;
1978+
}
1979+
1980+
bool has_return_type = fn->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE;
1981+
/* Variadic parameters are not counted as part of the standard number of arguments */
1982+
bool has_variadic_type = fn->common.fn_flags & ZEND_ACC_VARIADIC;
1983+
uint32_t num_args = fn->common.num_args + has_variadic_type;
1984+
/* TODO Only do allocation if need to resolve types, as arg_info is stored in SHM */
1985+
size_t allocated_size = sizeof(zend_arg_info) * (has_return_type + num_args);
1986+
1987+
zend_arg_info *new_arg_infos = zend_arena_alloc(&CG(arena), allocated_size);
1988+
memcpy(new_arg_infos, fn->common.arg_info - has_return_type, allocated_size);
1989+
fn->common.arg_info = new_arg_infos + has_return_type;
1990+
1991+
for (uint32_t i = 0; i < num_args + has_return_type; i++) {
1992+
zend_type type = new_arg_infos[i].type;
1993+
new_arg_infos[i].type = zend_resolve_single_type(type, ce);
1994+
}
1995+
}
1996+
19011997
static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_string *key, zend_function *fn) /* {{{ */
19021998
{
19031999
zend_function *existing_fn = NULL;
@@ -1947,10 +2043,12 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
19472043
if (UNEXPECTED(fn->type == ZEND_INTERNAL_FUNCTION)) {
19482044
new_fn = zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
19492045
memcpy(new_fn, fn, sizeof(zend_internal_function));
2046+
zend_resolve_trait_relative_class_types(new_fn, ce);
19502047
new_fn->common.fn_flags |= ZEND_ACC_ARENA_ALLOCATED;
19512048
} else {
19522049
new_fn = zend_arena_alloc(&CG(arena), sizeof(zend_op_array));
19532050
memcpy(new_fn, fn, sizeof(zend_op_array));
2051+
zend_resolve_trait_relative_class_types(new_fn, ce);
19542052
new_fn->op_array.fn_flags |= ZEND_ACC_TRAIT_CLONE;
19552053
new_fn->op_array.fn_flags &= ~ZEND_ACC_IMMUTABLE;
19562054
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
--TEST--
2+
ReflectionTypes of relative class types (self, parent) in classes, parameter types
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
8+
}
9+
10+
class B extends A {
11+
public function foo(self ...$o) { return $o; }
12+
}
13+
14+
class C extends B {
15+
public function bar(parent ...$o) { return $o; }
16+
public function ping(self ...$o) { return $o; }
17+
}
18+
19+
class D extends C {}
20+
21+
$instances = [
22+
new A,
23+
new B,
24+
new C,
25+
new D,
26+
];
27+
28+
foreach ($instances as $instance) {
29+
echo 'Class: ', $instance::class, PHP_EOL;
30+
$rc = new ReflectionClass($instance);
31+
$methods = $rc->getMethods();
32+
foreach ($methods as $method) {
33+
echo "\tMethod: ", $method->name, PHP_EOL;
34+
$parameters = $method->getParameters();
35+
foreach ($parameters as $param) {
36+
$type = $param->getType();
37+
echo "\t\tType: ", $type, PHP_EOL;
38+
echo "\t\tInstance of: ", $type::class, PHP_EOL;
39+
$resolvedType = $type->resolveToNamedType();
40+
echo "\t\t\tResolved Type: ", $resolvedType, PHP_EOL;
41+
echo "\t\t\tInstance of: ", $resolvedType::class, PHP_EOL;
42+
43+
foreach ($instances as $arg) {
44+
try {
45+
$instance->{$method->name}($arg);
46+
} catch (\TypeError $e) {
47+
echo "\t\t\t\t", $e->getMessage(), PHP_EOL;
48+
}
49+
}
50+
}
51+
}
52+
}
53+
54+
?>
55+
--EXPECTF--
56+
Class: A
57+
Class: B
58+
Method: foo
59+
Type: self
60+
Instance of: ReflectionRelativeClassType
61+
Resolved Type: B
62+
Instance of: ReflectionNamedType
63+
B::foo(): Argument #1 must be of type B, A given, called in %s on line %d
64+
Class: C
65+
Method: bar
66+
Type: parent
67+
Instance of: ReflectionRelativeClassType
68+
Resolved Type: B
69+
Instance of: ReflectionNamedType
70+
C::bar(): Argument #1 must be of type B, A given, called in %s on line %d
71+
Method: ping
72+
Type: self
73+
Instance of: ReflectionRelativeClassType
74+
Resolved Type: C
75+
Instance of: ReflectionNamedType
76+
C::ping(): Argument #1 must be of type C, A given, called in %s on line %d
77+
C::ping(): Argument #1 must be of type C, B given, called in %s on line %d
78+
Method: foo
79+
Type: self
80+
Instance of: ReflectionRelativeClassType
81+
Resolved Type: B
82+
Instance of: ReflectionNamedType
83+
B::foo(): Argument #1 must be of type B, A given, called in %s on line %d
84+
Class: D
85+
Method: bar
86+
Type: parent
87+
Instance of: ReflectionRelativeClassType
88+
Resolved Type: B
89+
Instance of: ReflectionNamedType
90+
C::bar(): Argument #1 must be of type B, A given, called in %s on line %d
91+
Method: ping
92+
Type: self
93+
Instance of: ReflectionRelativeClassType
94+
Resolved Type: C
95+
Instance of: ReflectionNamedType
96+
C::ping(): Argument #1 must be of type C, A given, called in %s on line %d
97+
C::ping(): Argument #1 must be of type C, B given, called in %s on line %d
98+
Method: foo
99+
Type: self
100+
Instance of: ReflectionRelativeClassType
101+
Resolved Type: B
102+
Instance of: ReflectionNamedType
103+
B::foo(): Argument #1 must be of type B, A given, called in %s on line %d
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
--TEST--
2+
ReflectionTypes of relative class types (self, parent, static) from traits in classes, return types
3+
--FILE--
4+
<?php
5+
6+
trait TraitExample {
7+
public function bar(object $o): parent { return $o; }
8+
public function ping(object $o): self { return $o; }
9+
public function pong(object $o): static { return $o; }
10+
}
11+
12+
class A {
13+
14+
}
15+
16+
class B extends A {
17+
}
18+
19+
class C extends B {
20+
use TraitExample;
21+
}
22+
23+
class D extends C {}
24+
25+
$instances = [
26+
new A,
27+
new B,
28+
new C,
29+
new D,
30+
];
31+
32+
foreach ($instances as $instance) {
33+
echo 'Class: ', $instance::class, PHP_EOL;
34+
$rc = new ReflectionClass($instance);
35+
$methods = $rc->getMethods();
36+
foreach ($methods as $method) {
37+
echo "\tMethod: ", $method->name, PHP_EOL;
38+
$type = $method->getReturnType();
39+
echo "\t\tType: ", $type, PHP_EOL;
40+
echo "\t\tInstance of: ", $type::class, PHP_EOL;
41+
$resolvedType = $type->resolveToNamedType();
42+
echo "\t\t\tResolved Type: ", $resolvedType, PHP_EOL;
43+
echo "\t\t\tInstance of: ", $resolvedType::class, PHP_EOL;
44+
45+
foreach ($instances as $arg) {
46+
try {
47+
$instance->{$method->name}($arg);
48+
} catch (\TypeError $e) {
49+
echo "\t\t\t\t", $e->getMessage(), PHP_EOL;
50+
}
51+
}
52+
}
53+
}
54+
55+
?>
56+
--EXPECT--
57+
Class: A
58+
Class: B
59+
Class: C
60+
Method: bar
61+
Type: parent
62+
Instance of: ReflectionRelativeClassType
63+
Resolved Type: B
64+
Instance of: ReflectionNamedType
65+
C::bar(): Return value must be of type B, A returned
66+
Method: ping
67+
Type: self
68+
Instance of: ReflectionRelativeClassType
69+
Resolved Type: C
70+
Instance of: ReflectionNamedType
71+
C::ping(): Return value must be of type C, A returned
72+
C::ping(): Return value must be of type C, B returned
73+
Method: pong
74+
Type: static
75+
Instance of: ReflectionRelativeClassType
76+
Resolved Type: C
77+
Instance of: ReflectionNamedType
78+
C::pong(): Return value must be of type C, A returned
79+
C::pong(): Return value must be of type C, B returned
80+
Class: D
81+
Method: bar
82+
Type: parent
83+
Instance of: ReflectionRelativeClassType
84+
Resolved Type: B
85+
Instance of: ReflectionNamedType
86+
C::bar(): Return value must be of type B, A returned
87+
Method: ping
88+
Type: self
89+
Instance of: ReflectionRelativeClassType
90+
Resolved Type: C
91+
Instance of: ReflectionNamedType
92+
C::ping(): Return value must be of type C, A returned
93+
C::ping(): Return value must be of type C, B returned
94+
Method: pong
95+
Type: static
96+
Instance of: ReflectionRelativeClassType
97+
Resolved Type: D
98+
Instance of: ReflectionNamedType
99+
C::pong(): Return value must be of type D, A returned
100+
C::pong(): Return value must be of type D, B returned
101+
C::pong(): Return value must be of type D, C returned

0 commit comments

Comments
 (0)