Skip to content

Commit 136c0b7

Browse files
committed
Make generic type declarations aware of the inner type
1 parent f0f61bf commit 136c0b7

4 files changed

Lines changed: 110 additions & 6 deletions

File tree

Zend/tests/type_declarations/abstract_generics/generic_parameter_declarations/property_basic.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ var_dump($t);
3737
--EXPECT--
3838
object(T)#3 (0) {
3939
["v"]=>
40-
uninitialized(I)
40+
uninitialized(I<string>)
4141
}
4242
object(T)#3 (1) {
4343
["v"]=>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
--TEST--
2+
Property generic class as type
3+
--FILE--
4+
<?php
5+
6+
interface I<T1, T2> {
7+
public function foo(T1 $param): T2;
8+
}
9+
10+
class CS implements I<string, string> {
11+
public function foo(string $param): string {
12+
return $param . '!';
13+
}
14+
}
15+
16+
class CI implements I<int, int> {
17+
public function foo(int $param): int {
18+
return $param + 42;
19+
}
20+
}
21+
22+
class T {
23+
public I<string, string> $v;
24+
}
25+
26+
$cs = new CS();
27+
$ci = new CI();
28+
29+
$t = new T();
30+
var_dump($t);
31+
$t->v = $cs;
32+
var_dump($t);
33+
$t->v = $ci;
34+
var_dump($t);
35+
36+
?>
37+
--EXPECT--
38+
object(T)#3 (0) {
39+
["v"]=>
40+
uninitialized(I<string, string>)
41+
}
42+
object(T)#3 (1) {
43+
["v"]=>
44+
object(CS)#1 (0) {
45+
}
46+
}
47+
object(T)#3 (1) {
48+
["v"]=>
49+
object(CI)#2 (0) {
50+
}
51+
}

Zend/zend_compile.c

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "zend_call_stack.h"
4040
#include "zend_frameless_function.h"
4141
#include "zend_property_hooks.h"
42+
#include "zend_smart_str.h"
4243

4344
#define SET_NODE(target, src) do { \
4445
target ## _type = (src)->op_type; \
@@ -1484,6 +1485,29 @@ zend_string *zend_type_to_string_resolved(const zend_type type, const zend_class
14841485
if (ZEND_TYPE_IS_INTERSECTION(type)) {
14851486
ZEND_ASSERT(!ZEND_TYPE_IS_UNION(type));
14861487
str = add_intersection_type(str, ZEND_TYPE_LIST(type), /* is_bracketed */ false);
1488+
} else if (ZEND_TYPE_IS_NAME_WITH_GENERIC_TYPES(type)) {
1489+
const zend_type *single_type;
1490+
bool is_class_name = true;
1491+
bool just_after_class_name = true;
1492+
1493+
smart_str class_with_generic = {0};
1494+
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), single_type) {
1495+
if (is_class_name) {
1496+
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*single_type));
1497+
smart_str_append(&class_with_generic, resolve_class_name(ZEND_TYPE_NAME(*single_type), scope));
1498+
smart_str_appendc(&class_with_generic, '<');
1499+
is_class_name = false;
1500+
continue;
1501+
}
1502+
if (!just_after_class_name) {
1503+
smart_str_appends(&class_with_generic, ", ");
1504+
}
1505+
just_after_class_name = false;
1506+
smart_str_append(&class_with_generic, zend_type_to_string_resolved(*single_type, scope, bound_types_to_scope));
1507+
} ZEND_TYPE_LIST_FOREACH_END();
1508+
smart_str_appendc(&class_with_generic, '>');
1509+
str = smart_str_extract(&class_with_generic);
1510+
smart_str_free(&class_with_generic);
14871511
} else if (ZEND_TYPE_HAS_LIST(type)) {
14881512
/* A union type might not be a list */
14891513
const zend_type *list_type;
@@ -7413,6 +7437,8 @@ ZEND_API void zend_set_function_arg_flags(zend_function *func) /* {{{ */
74137437
}
74147438
/* }}} */
74157439

7440+
static zend_type zend_compile_typename(zend_ast *ast);
7441+
74167442
static zend_type zend_compile_single_typename(zend_ast *ast)
74177443
{
74187444
const zend_class_entry *ce = CG(active_class_entry);
@@ -7512,7 +7538,32 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
75127538
class_name = zend_new_interned_string(class_name);
75137539
zend_alloc_ce_cache(class_name);
75147540
// TODO: use args_ast
7515-
return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, 0);
7541+
if (args_ast) {
7542+
ZEND_ASSERT(args_ast->kind == ZEND_AST_GENERIC_ARG_LIST);
7543+
const zend_ast_list *list = zend_ast_get_list(args_ast);
7544+
7545+
/* Allocate the type list directly on the arena as it must be a type
7546+
* list of the number of AST childs + 1 for the class name */
7547+
zend_type_list *type_list = zend_arena_alloc(&CG(arena), ZEND_TYPE_LIST_SIZE(list->children + 1));
7548+
type_list->num_types = 0;
7549+
/* Add type to the type list */
7550+
type_list->types[type_list->num_types++] = (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, 0);
7551+
7552+
for (uint32_t i = 0; i < list->children; i++) {
7553+
zend_ast *generic_arg_type_ast = list->child[i];
7554+
type_list->types[type_list->num_types++] = zend_compile_typename(generic_arg_type_ast);
7555+
}
7556+
ZEND_ASSERT(list->children+1 == type_list->num_types);
7557+
7558+
zend_type type = ZEND_TYPE_INIT_NONE(0);
7559+
ZEND_TYPE_SET_LIST(type, type_list);
7560+
/* Inform that the type list is a class name with bound types */
7561+
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_NAME_WITH_BOUND_TYPES;
7562+
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT;
7563+
return type;
7564+
} else {
7565+
return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, 0);
7566+
}
75167567
}
75177568
}
75187569
}
@@ -7599,8 +7650,6 @@ static void zend_is_type_list_redundant_by_single_type(const zend_type_list *typ
75997650
}
76007651
}
76017652

7602-
static zend_type zend_compile_typename(zend_ast *ast);
7603-
76047653
static zend_type zend_compile_typename_ex(
76057654
zend_ast *ast, bool force_allow_null, bool *forced_allow_null) /* {{{ */
76067655
{

Zend/zend_types.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,10 @@ typedef struct {
135135
zend_type constraint;
136136
} zend_generic_parameter;
137137

138-
#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 26
139-
#define _ZEND_TYPE_MASK ((1u << 26) - 1)
138+
#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 27
139+
#define _ZEND_TYPE_MASK ((1u << 27) - 1)
140140
/* Only one of these bits may be set. */
141+
#define _ZEND_TYPE_NAME_WITH_BOUND_TYPES (1u << 26)
141142
#define _ZEND_TYPE_GENERIC_PARAM_NAME_BIT (1u << 25)
142143
#define _ZEND_TYPE_NAME_BIT (1u << 24)
143144
// Used to signify that type.ptr is not a `zend_string*` but a `const char*`,
@@ -171,6 +172,9 @@ typedef struct {
171172
#define ZEND_TYPE_HAS_LITERAL_NAME(t) \
172173
((((t).type_mask) & _ZEND_TYPE_LITERAL_NAME_BIT) != 0)
173174

175+
#define ZEND_TYPE_IS_NAME_WITH_GENERIC_TYPES(t) \
176+
((((t).type_mask) & _ZEND_TYPE_NAME_WITH_BOUND_TYPES) != 0)
177+
174178
#define ZEND_TYPE_HAS_LIST(t) \
175179
((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0)
176180

0 commit comments

Comments
 (0)