Skip to content

Commit f199ac8

Browse files
committed
Represent scalar types as AST_TYPE
Per @tpunt's suggestion.
1 parent 0bd7cc8 commit f199ac8

File tree

4 files changed

+212
-13
lines changed

4 files changed

+212
-13
lines changed

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ ast\flags\PARAM_VARIADIC
219219
// Used by ast\AST_TYPE (exclusive)
220220
ast\flags\TYPE_ARRAY
221221
ast\flags\TYPE_CALLABLE
222+
ast\flags\TYPE_VOID // since version 40
223+
ast\flags\TYPE_BOOL // since version 40
224+
ast\flags\TYPE_LONG // since version 40
225+
ast\flags\TYPE_DOUBLE // since version 40
226+
ast\flags\TYPE_STRING // since version 40
222227
223228
// Used by ast\AST_CAST (exclusive)
224229
ast\flags\TYPE_NULL
@@ -232,9 +237,9 @@ ast\flags\TYPE_OBJECT
232237
// Used by ast\AST_UNARY_OP (exclusive)
233238
ast\flags\UNARY_BOOL_NOT
234239
ast\flags\UNARY_BITWISE_NOT
235-
ast\flags\UNARY_MINUS // since version 20
236-
ast\flags\UNARY_PLUS // since version 20
237-
ast\flags\UNARY_SILENCE // since version 20
240+
ast\flags\UNARY_MINUS // since version 20
241+
ast\flags\UNARY_PLUS // since version 20
242+
ast\flags\UNARY_SILENCE // since version 20
238243
239244
// Used by ast\AST_BINARY_OP and ast\AST_ASSIGN_OP in version >= 20 (exclusive)
240245
ast\flags\BINARY_BITWISE_OR
@@ -422,6 +427,8 @@ Version changelog
422427
call would return it as `'bar'`. Now always the latter form is used.
423428
* `null` elements are now stripped from `AST_STMT_LIST`. Previously these could be caused by nop
424429
statements (`;`).
430+
* Type hints `int`, `float`, `string`, `bool` and `void` will now be represented as `AST_TYPE`
431+
nodes with a respective flag.
425432

426433
### 30 (current)
427434

ast.c

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@
4242
#define AST_PLUS 261
4343
#define AST_MINUS 262
4444

45+
/* Define "void" for PHP 7.0 */
46+
#ifndef IS_VOID
47+
#define IS_VOID 18
48+
#endif
49+
4550
static inline void ast_update_property(zval *object, zend_string *name, zval *value, void **cache_slot) {
4651
zval name_zv;
4752
ZVAL_STR(&name_zv, name);
@@ -135,11 +140,49 @@ static inline zend_bool ast_is_name(zend_ast *ast, zend_ast *parent, uint32_t i)
135140
return 0;
136141
}
137142

143+
/* Assumes that ast_is_name is already true */
144+
static inline zend_bool ast_is_type(zend_ast *ast, zend_ast *parent, uint32_t i) {
145+
if (i == 0) {
146+
return parent->kind == ZEND_AST_PARAM;
147+
}
148+
if (i == 3) {
149+
return parent->kind == ZEND_AST_CLOSURE || parent->kind == ZEND_AST_FUNC_DECL
150+
|| parent->kind == ZEND_AST_METHOD;
151+
}
152+
return 0;
153+
}
154+
138155
static inline zend_bool ast_is_var_name(zend_ast *ast, zend_ast *parent, uint32_t i) {
139156
return (parent->kind == ZEND_AST_STATIC && i == 0)
140157
|| (parent->kind == ZEND_AST_CATCH && i == 1);
141158
}
142159

160+
/* Adopted from zend_compile.c */
161+
typedef struct _builtin_type_info {
162+
const char* name;
163+
const size_t name_len;
164+
const zend_uchar type;
165+
} builtin_type_info;
166+
static const builtin_type_info builtin_types[] = {
167+
{ZEND_STRL("int"), IS_LONG},
168+
{ZEND_STRL("float"), IS_DOUBLE},
169+
{ZEND_STRL("string"), IS_STRING},
170+
{ZEND_STRL("bool"), _IS_BOOL},
171+
{ZEND_STRL("void"), IS_VOID},
172+
{NULL, 0, IS_UNDEF}
173+
};
174+
static inline zend_uchar lookup_builtin_type(const zend_string *name) {
175+
const builtin_type_info *info = &builtin_types[0];
176+
for (; info->name; ++info) {
177+
if (ZSTR_LEN(name) == info->name_len
178+
&& !zend_binary_strcasecmp(ZSTR_VAL(name), ZSTR_LEN(name), info->name, info->name_len)
179+
) {
180+
return info->type;
181+
}
182+
}
183+
return 0;
184+
}
185+
143186
static inline zend_ast_attr ast_assign_op_to_binary_op(zend_ast_attr attr) {
144187
switch (attr) {
145188
case ZEND_ASSIGN_BW_OR: return ZEND_BW_OR;
@@ -173,32 +216,42 @@ static inline zend_ast **ast_get_children(zend_ast *ast, uint32_t *count) {
173216
}
174217
}
175218

176-
static void ast_create_virtual_node(
177-
zval *zv, zend_ast_kind kind, zend_ast *ast, zend_long version) {
219+
/* "child" may be AST_ZVAL or NULL */
220+
static void ast_create_virtual_node_ex(
221+
zval *zv, zend_ast_kind kind, zend_ast_attr attr, uint32_t lineno,
222+
zend_ast *child, zend_long version) {
178223
zval tmp_zv, tmp_zv2;
179224

180225
object_init_ex(zv, ast_node_ce);
181226

182227
ZVAL_LONG(&tmp_zv, kind);
183228
ast_update_property(zv, AST_STR(str_kind), &tmp_zv, AST_CACHE_SLOT_KIND);
184229

185-
ZVAL_LONG(&tmp_zv, ast->attr);
230+
ZVAL_LONG(&tmp_zv, attr);
186231
ast_update_property(zv, AST_STR(str_flags), &tmp_zv, AST_CACHE_SLOT_FLAGS);
187232

188-
ZVAL_LONG(&tmp_zv, zend_ast_get_lineno(ast));
233+
ZVAL_LONG(&tmp_zv, lineno);
189234
ast_update_property(zv, AST_STR(str_lineno), &tmp_zv, AST_CACHE_SLOT_LINENO);
190235

191236
array_init(&tmp_zv);
192237
ast_update_property(zv, AST_STR(str_children), &tmp_zv, AST_CACHE_SLOT_CHILDREN);
193238

194-
ZVAL_COPY(&tmp_zv2, zend_ast_get_zval(ast));
195-
if (version >= 30) {
196-
zend_hash_add_new(Z_ARRVAL(tmp_zv), ast_kind_child_name(kind, 0), &tmp_zv2);
197-
} else {
198-
zend_hash_next_index_insert(Z_ARRVAL(tmp_zv), &tmp_zv2);
239+
if (child) {
240+
ZVAL_COPY(&tmp_zv2, zend_ast_get_zval(child));
241+
if (version >= 30) {
242+
zend_hash_add_new(Z_ARRVAL(tmp_zv), ast_kind_child_name(kind, 0), &tmp_zv2);
243+
} else {
244+
zend_hash_next_index_insert(Z_ARRVAL(tmp_zv), &tmp_zv2);
245+
}
199246
}
200247
}
201248

249+
static void ast_create_virtual_node(
250+
zval *zv, zend_ast_kind kind, zend_ast *child, zend_long version) {
251+
return ast_create_virtual_node_ex(
252+
zv, kind, child->attr, zend_ast_get_lineno(child), child, version);
253+
}
254+
202255
static void ast_to_zval(zval *zv, zend_ast *ast, zend_long version);
203256

204257
static void ast_fill_children_ht(HashTable *ht, zend_ast *ast, zend_long version) {
@@ -222,6 +275,7 @@ static void ast_fill_children_ht(HashTable *ht, zend_ast *ast, zend_long version
222275
}
223276

224277
if (ast_is_name(child, ast, i)) {
278+
zend_uchar type;
225279
if (version >= 40 && child->attr == ZEND_NAME_FQ) {
226280
/* Ensure there is no leading \ for fully-qualified names. This can happen if
227281
* something like ('\bar')() is used. */
@@ -234,7 +288,14 @@ static void ast_fill_children_ht(HashTable *ht, zend_ast *ast, zend_long version
234288
}
235289
}
236290

237-
ast_create_virtual_node(&child_zv, AST_NAME, child, version);
291+
if (version >= 40 && child->attr == ZEND_NAME_NOT_FQ && ast_is_type(child, ast, i)
292+
&& (type = lookup_builtin_type(zend_ast_get_str(child)))) {
293+
/* Convert "int" etc typehints to TYPE nodes */
294+
ast_create_virtual_node_ex(
295+
&child_zv, ZEND_AST_TYPE, type, zend_ast_get_lineno(child), NULL, version);
296+
} else {
297+
ast_create_virtual_node(&child_zv, AST_NAME, child, version);
298+
}
238299
} else if (ast->kind == ZEND_AST_CLOSURE_USES) {
239300
ast_create_virtual_node(&child_zv, AST_CLOSURE_VAR, child, version);
240301
} else if (version >= 20 && ast_is_var_name(child, ast, i)) {
@@ -613,6 +674,7 @@ PHP_MINIT_FUNCTION(ast) {
613674
ast_register_flag_constant("TYPE_ARRAY", IS_ARRAY);
614675
ast_register_flag_constant("TYPE_OBJECT", IS_OBJECT);
615676
ast_register_flag_constant("TYPE_CALLABLE", IS_CALLABLE);
677+
ast_register_flag_constant("TYPE_VOID", IS_VOID);
616678

617679
ast_register_flag_constant("UNARY_BOOL_NOT", ZEND_BOOL_NOT);
618680
ast_register_flag_constant("UNARY_BITWISE_NOT", ZEND_BW_NOT);

tests/type_hints.phpt

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
--TEST--
2+
Type hints
3+
--FILE--
4+
<?php
5+
6+
require __DIR__ . '/../util.php';
7+
8+
$code = <<<'PHP'
9+
<?php
10+
function test(
11+
A $a, array $b, callable $c, INT $d, Float $e, string $f, bool $g
12+
) : void {
13+
}
14+
PHP;
15+
16+
echo ast_dump(ast\parse_code($code, $version=30)), "\n";
17+
echo ast_dump(ast\parse_code($code, $version=40)), "\n";
18+
19+
?>
20+
--EXPECT--
21+
AST_STMT_LIST
22+
0: AST_FUNC_DECL
23+
flags: 0
24+
name: test
25+
params: AST_PARAM_LIST
26+
0: AST_PARAM
27+
flags: 0
28+
type: AST_NAME
29+
flags: NAME_NOT_FQ (1)
30+
name: "A"
31+
name: "a"
32+
default: null
33+
1: AST_PARAM
34+
flags: 0
35+
type: AST_TYPE
36+
flags: TYPE_ARRAY (7)
37+
name: "b"
38+
default: null
39+
2: AST_PARAM
40+
flags: 0
41+
type: AST_TYPE
42+
flags: TYPE_CALLABLE (14)
43+
name: "c"
44+
default: null
45+
3: AST_PARAM
46+
flags: 0
47+
type: AST_NAME
48+
flags: NAME_NOT_FQ (1)
49+
name: "INT"
50+
name: "d"
51+
default: null
52+
4: AST_PARAM
53+
flags: 0
54+
type: AST_NAME
55+
flags: NAME_NOT_FQ (1)
56+
name: "Float"
57+
name: "e"
58+
default: null
59+
5: AST_PARAM
60+
flags: 0
61+
type: AST_NAME
62+
flags: NAME_NOT_FQ (1)
63+
name: "string"
64+
name: "f"
65+
default: null
66+
6: AST_PARAM
67+
flags: 0
68+
type: AST_NAME
69+
flags: NAME_NOT_FQ (1)
70+
name: "bool"
71+
name: "g"
72+
default: null
73+
uses: null
74+
stmts: AST_STMT_LIST
75+
returnType: AST_NAME
76+
flags: NAME_NOT_FQ (1)
77+
name: "void"
78+
AST_STMT_LIST
79+
0: AST_FUNC_DECL
80+
flags: 0
81+
name: test
82+
params: AST_PARAM_LIST
83+
0: AST_PARAM
84+
flags: 0
85+
type: AST_NAME
86+
flags: NAME_NOT_FQ (1)
87+
name: "A"
88+
name: "a"
89+
default: null
90+
1: AST_PARAM
91+
flags: 0
92+
type: AST_TYPE
93+
flags: TYPE_ARRAY (7)
94+
name: "b"
95+
default: null
96+
2: AST_PARAM
97+
flags: 0
98+
type: AST_TYPE
99+
flags: TYPE_CALLABLE (14)
100+
name: "c"
101+
default: null
102+
3: AST_PARAM
103+
flags: 0
104+
type: AST_TYPE
105+
flags: TYPE_LONG (4)
106+
name: "d"
107+
default: null
108+
4: AST_PARAM
109+
flags: 0
110+
type: AST_TYPE
111+
flags: TYPE_DOUBLE (5)
112+
name: "e"
113+
default: null
114+
5: AST_PARAM
115+
flags: 0
116+
type: AST_TYPE
117+
flags: TYPE_STRING (6)
118+
name: "f"
119+
default: null
120+
6: AST_PARAM
121+
flags: 0
122+
type: AST_TYPE
123+
flags: TYPE_BOOL (13)
124+
name: "g"
125+
default: null
126+
uses: null
127+
stmts: AST_STMT_LIST
128+
returnType: AST_TYPE
129+
flags: TYPE_VOID (18)

util.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ function get_flag_info() : array {
2828
flags\TYPE_ARRAY => 'TYPE_ARRAY',
2929
flags\TYPE_OBJECT => 'TYPE_OBJECT',
3030
flags\TYPE_CALLABLE => 'TYPE_CALLABLE',
31+
flags\TYPE_VOID => 'TYPE_VOID',
3132
];
3233
$useTypes = [
3334
flags\USE_NORMAL => 'USE_NORMAL',

0 commit comments

Comments
 (0)