Skip to content

Commit f0f61bf

Browse files
committed
Add syntax support for generic types in parameters and properties
1 parent db84adf commit f0f61bf

5 files changed

Lines changed: 136 additions & 6 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
Parameter generic class as type
3+
--FILE--
4+
<?php
5+
6+
interface I<T> {
7+
public function foo(T $param): T;
8+
}
9+
10+
class CS implements I<string> {
11+
public function foo(string $param): string {
12+
return $param . '!';
13+
}
14+
}
15+
16+
class CI implements I<int> {
17+
public function foo(int $param): int {
18+
return $param + 42;
19+
}
20+
}
21+
22+
function bar(I<string> $v) {
23+
var_dump($v);
24+
}
25+
26+
$cs = new CS();
27+
$ci = new CI();
28+
29+
bar($cs);
30+
bar($ci);
31+
32+
?>
33+
--EXPECT--
34+
object(CS)#1 (0) {
35+
}
36+
object(CI)#2 (0) {
37+
}
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<T> {
7+
public function foo(T $param): T;
8+
}
9+
10+
class CS implements I<string> {
11+
public function foo(string $param): string {
12+
return $param . '!';
13+
}
14+
}
15+
16+
class CI implements I<int> {
17+
public function foo(int $param): int {
18+
return $param + 42;
19+
}
20+
}
21+
22+
class T {
23+
public I<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)
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+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
Return generic class as type
3+
--FILE--
4+
<?php
5+
6+
interface I<T> {
7+
public function foo(T $param): T;
8+
}
9+
10+
class CS implements I<string> {
11+
public function foo(string $param): string {
12+
return $param . '!';
13+
}
14+
}
15+
16+
class CI implements I<int> {
17+
public function foo(int $param): int {
18+
return $param + 42;
19+
}
20+
}
21+
22+
function bar($v): I<string> {
23+
return $v;
24+
}
25+
26+
$cs = new CS();
27+
$ci = new CI();
28+
29+
var_dump(bar($cs));
30+
var_dump(bar($ci));
31+
32+
?>
33+
--EXPECT--
34+
object(CS)#1 (0) {
35+
}
36+
object(CI)#2 (0) {
37+
}

Zend/zend_compile.c

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7426,11 +7426,14 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
74267426

74277427
return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, 0, 0);
74287428
} else {
7429-
zend_string *type_name = zend_ast_get_str(ast);
7429+
ZEND_ASSERT(ast->kind == ZEND_AST_CLASS_REF);
7430+
zend_ast *name_ast = ast->child[0];
7431+
zend_ast *args_ast = ast->child[1];
7432+
zend_string *type_name = zend_ast_get_str(name_ast);
74307433
uint8_t type_code = zend_lookup_builtin_type_by_name(type_name);
74317434

74327435
if (type_code != 0) {
7433-
if ((ast->attr & ZEND_NAME_NOT_FQ) != ZEND_NAME_NOT_FQ) {
7436+
if ((name_ast->attr & ZEND_NAME_NOT_FQ) != ZEND_NAME_NOT_FQ) {
74347437
zend_error_noreturn(E_COMPILE_ERROR,
74357438
"Type declaration '%s' must be unqualified",
74367439
ZSTR_VAL(zend_string_tolower(type_name)));
@@ -7447,7 +7450,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
74477450
return (zend_type) ZEND_TYPE_INIT_CODE(type_code, 0, 0);
74487451
} else {
74497452
const char *correct_name;
7450-
uint32_t fetch_type = zend_get_class_fetch_type_ast(ast);
7453+
uint32_t fetch_type = zend_get_class_fetch_type_ast(name_ast);
74517454

74527455
if (ce && ce->num_generic_parameters > 0) {
74537456
for (uint32_t generic_param_index = 0; generic_param_index < ce->num_generic_parameters; generic_param_index++) {
@@ -7460,7 +7463,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
74607463

74617464
zend_string *class_name = type_name;
74627465
if (fetch_type == ZEND_FETCH_CLASS_DEFAULT) {
7463-
class_name = zend_resolve_class_name_ast(ast);
7466+
class_name = zend_resolve_class_name_ast(name_ast);
74647467
zend_assert_valid_class_name(class_name, "a type name");
74657468
} else {
74667469
ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_SELF || fetch_type == ZEND_FETCH_CLASS_PARENT);
@@ -7487,7 +7490,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
74877490
zend_string_addref(class_name);
74887491
}
74897492

7490-
if (ast->attr == ZEND_NAME_NOT_FQ
7493+
if (name_ast->attr == ZEND_NAME_NOT_FQ && !args_ast
74917494
&& zend_is_confusable_type(type_name, &correct_name)
74927495
&& zend_is_not_imported(type_name)) {
74937496
const char *extra =
@@ -7508,6 +7511,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
75087511

75097512
class_name = zend_new_interned_string(class_name);
75107513
zend_alloc_ce_cache(class_name);
7514+
// TODO: use args_ast
75117515
return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, 0);
75127516
}
75137517
}

Zend/zend_language_parser.y

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,8 @@ type_expr_without_static:
893893
type_without_static:
894894
T_ARRAY { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); }
895895
| T_CALLABLE { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_CALLABLE); }
896-
| name { $$ = $1; }
896+
| name { $$ = zend_ast_create(ZEND_AST_CLASS_REF, $1, NULL); }
897+
| name '<' generic_arg_list '>' { $$ = zend_ast_create(ZEND_AST_CLASS_REF, $1, $3); }
897898
;
898899

899900
union_type_without_static_element:

0 commit comments

Comments
 (0)