Skip to content

Commit f0f387c

Browse files
committed
Something works...
1 parent b25d29c commit f0f387c

File tree

8 files changed

+227
-11
lines changed

8 files changed

+227
-11
lines changed

Zend/tests/generics/basic.phpt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
Basic generic class declaration
3+
--FILE--
4+
<?php
5+
6+
interface I<T> {
7+
}
8+
9+
class C<T> {
10+
}
11+
12+
final class F<T> {
13+
}
14+
15+
trait T<T> {
16+
}
17+
18+
?>
19+
--EXPECT--
20+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
Duplicate generic parameter name
3+
--FILE--
4+
<?php
5+
6+
class Test<T, T> {
7+
}
8+
9+
?>
10+
--EXPECTF--
11+
Fatal error: Duplicate generic parameter T in %s on line %d
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Bind direct parent parameter during inheritance
3+
--FILE--
4+
<?php
5+
6+
abstract class WithParam<T> {
7+
public function method(T $param) {
8+
var_dump($param);
9+
}
10+
}
11+
12+
class Concrete extends WithParam<int> {
13+
}
14+
15+
$obj = new Concrete;
16+
$obj->method(42);
17+
18+
try {
19+
$obj->method("string");
20+
} catch (TypeError $e) {
21+
echo $e->getMessage(), "\n";
22+
}
23+
24+
?>
25+
--EXPECTF--
26+
int(42)
27+
Argument 1 passed to WithParam::method() must be of type T (where T = int), string given, called in %s on line %d

Zend/zend.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ typedef struct _zend_trait_alias {
107107
uint32_t modifiers;
108108
} zend_trait_alias;
109109

110+
typedef struct _zend_generic_param {
111+
zend_string *name;
112+
zend_type type;
113+
} zend_generic_param;
114+
110115
struct _zend_class_entry {
111116
char type;
112117
zend_string *name;
@@ -171,6 +176,17 @@ struct _zend_class_entry {
171176
zend_trait_alias **trait_aliases;
172177
zend_trait_precedence **trait_precedences;
173178

179+
/* generic_params are the free generic parameters on this class.
180+
* parent_generic_args are the bound generic parameters of parent classes.
181+
* Pre-inheritance, this only includes what we pass to the direct parent.
182+
* During inheritance, any bound parameters from parent parameters will be
183+
* included before our own, and all generic parameter IDs will be shifted
184+
* accordingly. */
185+
uint32_t num_generic_params;
186+
uint32_t num_parent_generic_args;
187+
zend_generic_param *generic_params;
188+
zend_type *parent_generic_args;
189+
174190
union {
175191
struct {
176192
zend_string *filename;

Zend/zend_compile.c

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,6 +1177,9 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
11771177
str = zend_string_copy(resolve_class_name(ZEND_TYPE_NAME(type), scope));
11781178
} else if (ZEND_TYPE_HAS_CE(type)) {
11791179
str = zend_string_copy(ZEND_TYPE_CE(type)->name);
1180+
} else if (ZEND_TYPE_HAS_GENERIC_PARAM(type)) {
1181+
zend_generic_param *param = &scope->generic_params[ZEND_TYPE_GENERIC_PARAM_ID(type)];
1182+
str = zend_string_copy(param->name);
11801183
}
11811184

11821185
uint32_t type_mask = ZEND_TYPE_FULL_MASK(type);
@@ -1519,6 +1522,39 @@ static zend_string *zend_resolve_const_class_name_reference(zend_ast *class_ast,
15191522
return zend_resolve_class_name(class_name, name_ast->attr);
15201523
}
15211524

1525+
static zend_type zend_compile_typename(
1526+
zend_ast *ast, zend_bool force_allow_null, zend_bool use_arena);
1527+
1528+
static zend_type *zend_compile_generic_args(zend_ast *args_ast, uint32_t *num_args) {
1529+
zend_ast_list *list = zend_ast_get_list(args_ast);
1530+
zend_type *types = emalloc(sizeof(zend_type) * list->children);
1531+
*num_args = list->children;
1532+
for (uint32_t i = 0; i < list->children; i++) {
1533+
zend_ast *type_ast = list->child[0];
1534+
types[i] = zend_compile_typename(type_ast, 0, 0);
1535+
}
1536+
return types;
1537+
}
1538+
1539+
static zend_string *zend_compile_const_class_name_reference(
1540+
zend_ast *class_ast, const char *type, uint32_t *num_generic_args, zend_type **generic_args) {
1541+
zend_ast *name_ast = class_ast->child[0];
1542+
zend_string *class_name = zend_ast_get_str(name_ast);
1543+
if (ZEND_FETCH_CLASS_DEFAULT != zend_get_class_fetch_type_ast(name_ast)) {
1544+
zend_error_noreturn(E_COMPILE_ERROR,
1545+
"Cannot use '%s' as %s, as it is reserved",
1546+
ZSTR_VAL(class_name), type);
1547+
}
1548+
class_name = zend_resolve_class_name(class_name, name_ast->attr);
1549+
if (class_ast->child[1]) {
1550+
*generic_args = zend_compile_generic_args(class_ast->child[1], num_generic_args);
1551+
} else {
1552+
*num_generic_args = 0;
1553+
*generic_args = NULL;
1554+
}
1555+
return class_name;
1556+
}
1557+
15221558
static void zend_ensure_valid_class_fetch_type(uint32_t fetch_type) /* {{{ */
15231559
{
15241560
if (fetch_type != ZEND_FETCH_CLASS_DEFAULT && zend_is_scope_known()) {
@@ -1833,6 +1869,8 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, zend_bool nullify
18331869
ce->num_interfaces = 0;
18341870
ce->interfaces = NULL;
18351871
ce->num_traits = 0;
1872+
ce->num_generic_params = 0;
1873+
ce->num_parent_generic_args = 0;
18361874
ce->trait_names = NULL;
18371875
ce->trait_aliases = NULL;
18381876
ce->trait_precedences = NULL;
@@ -5483,6 +5521,20 @@ ZEND_API void zend_set_function_arg_flags(zend_function *func) /* {{{ */
54835521
}
54845522
/* }}} */
54855523

5524+
static uint32_t lookup_generic_param_id(zend_string *name) {
5525+
if (!CG(active_class_entry) || CG(active_class_entry)->num_generic_params == 0) {
5526+
return (uint32_t) -1;
5527+
}
5528+
5529+
for (uint32_t i = 0; i < CG(active_class_entry)->num_generic_params; i++) {
5530+
zend_generic_param *param = &CG(active_class_entry)->generic_params[i];
5531+
if (zend_string_equals(param->name, name)) {
5532+
return i;
5533+
}
5534+
}
5535+
return (uint32_t) -1;
5536+
}
5537+
54865538
static zend_type zend_compile_single_typename(zend_ast *ast)
54875539
{
54885540
ZEND_ASSERT(!(ast->attr & ZEND_TYPE_NULLABLE));
@@ -5502,6 +5554,11 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
55025554
} else {
55035555
const char *correct_name;
55045556
zend_string *orig_name = zend_ast_get_str(ast);
5557+
uint32_t generic_param_id = lookup_generic_param_id(orig_name);
5558+
if (ast->attr == ZEND_NAME_NOT_FQ && generic_param_id != (uint32_t) -1) {
5559+
return (zend_type) ZEND_TYPE_INIT_GENERIC_PARAM(generic_param_id, 0);
5560+
}
5561+
55055562
uint32_t fetch_type = zend_get_class_fetch_type_ast(ast);
55065563
if (fetch_type == ZEND_FETCH_CLASS_DEFAULT) {
55075564
class_name = zend_resolve_class_name_ast(ast);
@@ -6552,12 +6609,40 @@ static zend_string *zend_generate_anon_class_name(uint32_t start_lineno) /* {{{
65526609
}
65536610
/* }}} */
65546611

6612+
static void zend_compile_generic_params(zend_ast *params_ast)
6613+
{
6614+
zend_ast_list *list = zend_ast_get_list(params_ast);
6615+
zend_generic_param *generic_params = emalloc(list->children * sizeof(zend_generic_param));
6616+
for (uint32_t i = 0; i < list->children; i++) {
6617+
zend_ast *param_ast = list->child[i];
6618+
zend_string *name = zend_ast_get_str(param_ast->child[0]);
6619+
for (uint32_t j = 0; j < i; j++) {
6620+
if (zend_string_equals(name, generic_params[j].name)) {
6621+
zend_error(E_COMPILE_ERROR, "Duplicate generic parameter %s", ZSTR_VAL(name));
6622+
}
6623+
}
6624+
6625+
generic_params[i].name = zend_string_copy(name);
6626+
if (param_ast->child[1]) {
6627+
generic_params[i].type = zend_compile_typename(param_ast->child[1], 0, 0);
6628+
// TODO: Validate potential additional constraints on the type bound.
6629+
// For example, can "void" be used?
6630+
} else {
6631+
generic_params[i].type = (zend_type) ZEND_TYPE_INIT_NONE(0);
6632+
}
6633+
}
6634+
6635+
CG(active_class_entry)->num_generic_params = list->children;
6636+
CG(active_class_entry)->generic_params = generic_params;
6637+
}
6638+
65556639
zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */
65566640
{
65576641
zend_ast_decl *decl = (zend_ast_decl *) ast;
65586642
zend_ast *extends_ast = decl->child[0];
65596643
zend_ast *implements_ast = decl->child[1];
65606644
zend_ast *stmt_ast = decl->child[2];
6645+
zend_ast *generic_params_ast = decl->child[3];
65616646
zend_string *name, *lcname;
65626647
zend_class_entry *ce = zend_arena_alloc(&CG(arena), sizeof(zend_class_entry));
65636648
zend_op *opline;
@@ -6617,13 +6702,17 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */
66176702
}
66186703

66196704
if (extends_ast) {
6620-
ce->parent_name =
6621-
zend_resolve_const_class_name_reference(extends_ast, "class name");
6705+
ce->parent_name = zend_compile_const_class_name_reference(
6706+
extends_ast, "class name", &ce->num_parent_generic_args, &ce->parent_generic_args);
66226707
ce->ce_flags |= ZEND_ACC_INHERITED;
66236708
}
66246709

66256710
CG(active_class_entry) = ce;
66266711

6712+
if (generic_params_ast) {
6713+
zend_compile_generic_params(generic_params_ast);
6714+
}
6715+
66276716
zend_compile_stmt(stmt_ast);
66286717

66296718
/* Reset lineno for final opcodes and errors */

Zend/zend_execute.c

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -640,10 +640,10 @@ static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_throw_non_object_erro
640640
}
641641
}
642642

643-
/* Test used to preserve old error messages for non-union types.
643+
/* Test used to preserve old error messages for simple types.
644644
* We might want to canonicalize all type errors instead. */
645-
static zend_bool is_union_type(zend_type type) {
646-
if (ZEND_TYPE_HAS_LIST(type)) {
645+
static zend_bool is_complex_type(zend_type type) {
646+
if (ZEND_TYPE_HAS_LIST(type) || ZEND_TYPE_HAS_GENERIC_PARAM(type)) {
647647
return 1;
648648
}
649649
uint32_t type_mask_without_null = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type);
@@ -673,11 +673,26 @@ static ZEND_COLD void zend_verify_type_error_common(
673673
*fclass = "";
674674
}
675675

676-
if (is_union_type(arg_info->type)) {
677-
zend_string *type_str = zend_type_to_string(arg_info->type);
676+
if (is_complex_type(arg_info->type)) {
677+
zend_string *type_str = zend_type_to_string_resolved(arg_info->type, zf->common.scope);
678678
smart_str_appends(&str, "be of type ");
679679
smart_str_append(&str, type_str);
680680
zend_string_release(type_str);
681+
682+
if (ZEND_TYPE_HAS_GENERIC_PARAM(arg_info->type)) {
683+
// TODO: It's all a hack...
684+
zend_class_entry *called_scope = zend_get_called_scope(EG(current_execute_data));
685+
uint32_t generic_param_id = ZEND_TYPE_GENERIC_PARAM_ID(arg_info->type);
686+
zend_generic_param *param = &zf->common.scope->generic_params[generic_param_id];
687+
zend_type real_type = called_scope->parent_generic_args[generic_param_id];
688+
zend_string *real_type_string = zend_type_to_string(real_type);
689+
smart_str_appends(&str, " (where ");
690+
smart_str_append(&str, param->name);
691+
smart_str_appends(&str, " = ");
692+
smart_str_append(&str, real_type_string);
693+
smart_str_appendc(&str, ')');
694+
zend_string_release(real_type_string);
695+
}
681696
} else if (ZEND_TYPE_HAS_CLASS(arg_info->type)) {
682697
zend_bool is_interface = 0;
683698
zend_class_entry *ce = *cache_slot;
@@ -1067,6 +1082,19 @@ static zend_always_inline zend_bool zend_check_type_slow(
10671082
}
10681083

10691084
builtin_types:
1085+
if (ZEND_TYPE_HAS_GENERIC_PARAM(type)) {
1086+
// TODO: This doesn't handle free generic parameters.
1087+
uint32_t param_id = ZEND_TYPE_GENERIC_PARAM_ID(type);
1088+
zend_class_entry *called_scope = zend_get_called_scope(EG(current_execute_data));
1089+
ZEND_ASSERT(param_id < called_scope->num_parent_generic_args);
1090+
zend_type real_type = called_scope->parent_generic_args[param_id];
1091+
if (ZEND_TYPE_CONTAINS_CODE(real_type, Z_TYPE_P(arg))) {
1092+
return 1;
1093+
}
1094+
return zend_check_type_slow(
1095+
real_type, arg, ref, cache_slot, scope, is_return_type, is_internal);
1096+
}
1097+
10701098
type_mask = ZEND_TYPE_FULL_MASK(type);
10711099
if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL)) {
10721100
return 1;

Zend/zend_opcode.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,21 @@ ZEND_API void destroy_zend_class(zval *zv)
354354
if (ce->num_traits > 0) {
355355
_destroy_zend_class_traits_info(ce);
356356
}
357+
if (ce->num_generic_params > 0) {
358+
for (uint32_t i = 0; i < ce->num_generic_params; i++) {
359+
zend_generic_param *param = &ce->generic_params[i];
360+
zend_string_release(param->name);
361+
zend_type_release(param->type, /* persistent */ 0);
362+
}
363+
efree(ce->generic_params);
364+
}
365+
if (ce->num_parent_generic_args > 0) {
366+
for (uint32_t i = 0; i < ce->num_parent_generic_args; i++) {
367+
zend_type *type = &ce->parent_generic_args[i];
368+
zend_type_release(*type, /* persistent */ 0);
369+
}
370+
efree(ce->parent_generic_args);
371+
}
357372

358373
break;
359374
case ZEND_INTERNAL_CLASS:

Zend/zend_types.h

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,19 +141,20 @@ typedef struct {
141141
#define _ZEND_TYPE_NAME_BIT (1u << 23)
142142
#define _ZEND_TYPE_CE_BIT (1u << 22)
143143
#define _ZEND_TYPE_LIST_BIT (1u << 21)
144-
#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_CE_BIT|_ZEND_TYPE_NAME_BIT)
144+
#define _ZEND_TYPE_CLASS_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_CE_BIT|_ZEND_TYPE_NAME_BIT)
145145
/* Whether the type list is arena allocated */
146146
#define _ZEND_TYPE_ARENA_BIT (1u << 20)
147+
#define _ZEND_TYPE_GENERIC_PARAM_BIT (1u << 19)
147148
/* Type mask excluding the flags above. */
148-
#define _ZEND_TYPE_MAY_BE_MASK ((1u << 20) - 1)
149+
#define _ZEND_TYPE_MAY_BE_MASK ((1u << 19) - 1)
149150
/* Must have same value as MAY_BE_NULL */
150151
#define _ZEND_TYPE_NULLABLE_BIT 0x2
151152

152153
#define ZEND_TYPE_IS_SET(t) \
153154
(((t).type_mask & _ZEND_TYPE_MASK) != 0)
154155

155156
#define ZEND_TYPE_HAS_CLASS(t) \
156-
((((t).type_mask) & _ZEND_TYPE_KIND_MASK) != 0)
157+
((((t).type_mask) & _ZEND_TYPE_CLASS_MASK) != 0)
157158

158159
#define ZEND_TYPE_HAS_CE(t) \
159160
((((t).type_mask) & _ZEND_TYPE_CE_BIT) != 0)
@@ -164,6 +165,9 @@ typedef struct {
164165
#define ZEND_TYPE_HAS_LIST(t) \
165166
((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0)
166167

168+
#define ZEND_TYPE_HAS_GENERIC_PARAM(t) \
169+
((((t).type_mask) & _ZEND_TYPE_GENERIC_PARAM_BIT) != 0)
170+
167171
#define ZEND_TYPE_USES_ARENA(t) \
168172
((((t).type_mask) & _ZEND_TYPE_ARENA_BIT) != 0)
169173

@@ -182,6 +186,9 @@ typedef struct {
182186
#define ZEND_TYPE_LIST(t) \
183187
((zend_type_list *) (t).ptr)
184188

189+
#define ZEND_TYPE_GENERIC_PARAM_ID(t) \
190+
((uint32_t) (uintptr_t) (t).ptr)
191+
185192
/* Type lists use the low bit to distinguish NAME and CE entries,
186193
* both of which may exist in the same list. */
187194
#define ZEND_TYPE_LIST_IS_CE(entry) \
@@ -226,7 +233,7 @@ typedef struct {
226233

227234
#define ZEND_TYPE_SET_PTR_AND_KIND(t, _ptr, kind_bit) do { \
228235
(t).ptr = (_ptr); \
229-
(t).type_mask &= ~_ZEND_TYPE_KIND_MASK; \
236+
(t).type_mask &= ~_ZEND_TYPE_CLASS_MASK; \
230237
(t).type_mask |= (kind_bit); \
231238
} while (0)
232239

@@ -285,6 +292,9 @@ typedef struct {
285292
#define ZEND_TYPE_INIT_CLASS_CONST_MASK(class_name, type_mask) \
286293
ZEND_TYPE_INIT_PTR_MASK(class_name, _ZEND_TYPE_NAME_BIT | (type_mask))
287294

295+
#define ZEND_TYPE_INIT_GENERIC_PARAM(param_id, extra_flags) \
296+
{ (void *) (uintptr_t) param_id, _ZEND_TYPE_GENERIC_PARAM_BIT | (extra_flags) }
297+
288298
typedef union _zend_value {
289299
zend_long lval; /* long value */
290300
double dval; /* double value */

0 commit comments

Comments
 (0)