Skip to content

Commit fc5048f

Browse files
committed
Support nullable types
Represent these using a new AST_NULLABLE_TYPE virtual node.
1 parent f308dce commit fc5048f

File tree

7 files changed

+102
-14
lines changed

7 files changed

+102
-14
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ AST_METHOD_REFERENCE: class, method
361361
AST_NAME: name
362362
AST_NAMESPACE: name, stmts
363363
AST_NEW: class, args
364+
AST_NULLABLE_TYPE: type // Used only in PHP 7.1
364365
AST_OR: left, right // Prior to version 20
365366
AST_PARAM: type, name, default
366367
AST_POST_DEC: var

ast.c

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,8 @@ static void ast_to_zval(zval *zv, zend_ast *ast, zend_long version);
245245

246246
static void ast_create_virtual_node_ex(
247247
zval *zv, zend_ast_kind kind, zend_ast_attr attr, uint32_t lineno,
248-
zend_ast *child, zend_long version) {
249-
zval tmp_zv, tmp_zv2;
248+
zval *child_zv, zend_long version) {
249+
zval tmp_zv;
250250

251251
object_init_ex(zv, ast_node_ce);
252252

@@ -262,21 +262,22 @@ static void ast_create_virtual_node_ex(
262262
array_init(&tmp_zv);
263263
ast_update_property(zv, AST_STR(str_children), &tmp_zv, AST_CACHE_SLOT_CHILDREN);
264264

265-
if (child) {
265+
if (child_zv) {
266266
zend_string *child_name = version >= 30 ? ast_kind_child_name(kind, 0) : NULL;
267-
ast_to_zval(&tmp_zv2, child, version);
268267
if (child_name) {
269-
zend_hash_add_new(Z_ARRVAL(tmp_zv), child_name, &tmp_zv2);
268+
zend_hash_add_new(Z_ARRVAL(tmp_zv), child_name, child_zv);
270269
} else {
271-
zend_hash_next_index_insert(Z_ARRVAL(tmp_zv), &tmp_zv2);
270+
zend_hash_next_index_insert(Z_ARRVAL(tmp_zv), child_zv);
272271
}
273272
}
274273
}
275274

276275
static void ast_create_virtual_node(
277276
zval *zv, zend_ast_kind kind, zend_ast *child, zend_long version) {
277+
zval child_zv;
278+
ast_to_zval(&child_zv, child, version);
278279
return ast_create_virtual_node_ex(
279-
zv, kind, child->attr, zend_ast_get_lineno(child), child, version);
280+
zv, kind, child->attr, zend_ast_get_lineno(child), &child_zv, version);
280281
}
281282

282283
static void ast_fill_children_ht(HashTable *ht, zend_ast *ast, zend_long version) {
@@ -301,6 +302,12 @@ static void ast_fill_children_ht(HashTable *ht, zend_ast *ast, zend_long version
301302

302303
if (ast_is_name(child, ast, i)) {
303304
zend_uchar type;
305+
zend_bool is_nullable = 0;
306+
if (child->attr & ZEND_TYPE_NULLABLE) {
307+
is_nullable = 1;
308+
child->attr &= ~ZEND_TYPE_NULLABLE;
309+
}
310+
304311
if (version >= 40 && child->attr == ZEND_NAME_FQ) {
305312
/* Ensure there is no leading \ for fully-qualified names. This can happen if
306313
* something like ('\bar')() is used. */
@@ -321,14 +328,28 @@ static void ast_fill_children_ht(HashTable *ht, zend_ast *ast, zend_long version
321328
} else {
322329
ast_create_virtual_node(&child_zv, AST_NAME, child, version);
323330
}
331+
332+
if (is_nullable) {
333+
/* Create explicit AST_NULLABLE_TYPE node */
334+
zval tmp;
335+
ZVAL_COPY_VALUE(&tmp, &child_zv);
336+
ast_create_virtual_node_ex(
337+
&child_zv, AST_NULLABLE_TYPE, 0, zend_ast_get_lineno(child), &tmp, version);
338+
}
324339
} else if (ast->kind == ZEND_AST_CLOSURE_USES) {
325340
ast_create_virtual_node(&child_zv, AST_CLOSURE_VAR, child, version);
326341
} else if (version >= 20 && ast_is_var_name(child, ast, i)) {
327342
ast_create_virtual_node(&child_zv, ZEND_AST_VAR, child, version);
328343
} else if (version >= 40 && ast_should_normalize_list(child, ast, i)) {
329-
ast_create_virtual_node_ex(
330-
&child_zv, ZEND_AST_STMT_LIST, 0,
331-
zend_ast_get_lineno(child ? child : ast), child, version);
344+
if (child) {
345+
zval tmp;
346+
ast_to_zval(&tmp, child, version);
347+
ast_create_virtual_node_ex(
348+
&child_zv, ZEND_AST_STMT_LIST, 0, zend_ast_get_lineno(child), &tmp, version);
349+
} else {
350+
ast_create_virtual_node_ex(
351+
&child_zv, ZEND_AST_STMT_LIST, 0, zend_ast_get_lineno(ast), NULL, version);
352+
}
332353
} else if (i == 2
333354
&& (ast->kind == ZEND_AST_PROP_ELEM || ast->kind == ZEND_AST_CONST_ELEM)) {
334355
/* Skip docComment child -- It's handled separately */

ast_data.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#include "php_ast.h"
22

3-
const size_t ast_kinds_count = 98;
3+
const size_t ast_kinds_count = 99;
44

55
const zend_ast_kind ast_kinds[] = {
66
ZEND_AST_ARG_LIST,
@@ -22,6 +22,7 @@ const zend_ast_kind ast_kinds[] = {
2222
ZEND_AST_USE,
2323
AST_NAME,
2424
AST_CLOSURE_VAR,
25+
AST_NULLABLE_TYPE,
2526
ZEND_AST_FUNC_DECL,
2627
ZEND_AST_CLOSURE,
2728
ZEND_AST_METHOD,
@@ -124,6 +125,7 @@ const char *ast_kind_to_name(zend_ast_kind kind) {
124125
case ZEND_AST_USE: return "AST_USE";
125126
case AST_NAME: return "AST_NAME";
126127
case AST_CLOSURE_VAR: return "AST_CLOSURE_VAR";
128+
case AST_NULLABLE_TYPE: return "AST_NULLABLE_TYPE";
127129
case ZEND_AST_FUNC_DECL: return "AST_FUNC_DECL";
128130
case ZEND_AST_CLOSURE: return "AST_CLOSURE";
129131
case ZEND_AST_METHOD: return "AST_METHOD";
@@ -220,6 +222,11 @@ zend_string *ast_kind_child_name(zend_ast_kind kind, uint32_t child) {
220222
case 0: return AST_STR(str_name);
221223
}
222224
return NULL;
225+
case AST_NULLABLE_TYPE:
226+
switch (child) {
227+
case 0: return AST_STR(str_type);
228+
}
229+
return NULL;
223230
case ZEND_AST_FUNC_DECL:
224231
switch (child) {
225232
case 0: return AST_STR(str_params);
@@ -697,6 +704,7 @@ void ast_register_kind_constants(INIT_FUNC_ARGS) {
697704
REGISTER_NS_LONG_CONSTANT("ast", "AST_USE", ZEND_AST_USE, CONST_CS | CONST_PERSISTENT);
698705
REGISTER_NS_LONG_CONSTANT("ast", "AST_NAME", AST_NAME, CONST_CS | CONST_PERSISTENT);
699706
REGISTER_NS_LONG_CONSTANT("ast", "AST_CLOSURE_VAR", AST_CLOSURE_VAR, CONST_CS | CONST_PERSISTENT);
707+
REGISTER_NS_LONG_CONSTANT("ast", "AST_NULLABLE_TYPE", AST_NULLABLE_TYPE, CONST_CS | CONST_PERSISTENT);
700708
REGISTER_NS_LONG_CONSTANT("ast", "AST_FUNC_DECL", ZEND_AST_FUNC_DECL, CONST_CS | CONST_PERSISTENT);
701709
REGISTER_NS_LONG_CONSTANT("ast", "AST_CLOSURE", ZEND_AST_CLOSURE, CONST_CS | CONST_PERSISTENT);
702710
REGISTER_NS_LONG_CONSTANT("ast", "AST_METHOD", ZEND_AST_METHOD, CONST_CS | CONST_PERSISTENT);

ast_str_defs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
X(name) \
1010
X(docComment) \
1111
X(endLineno) \
12+
X(type) \
1213
X(params) \
1314
X(uses) \
1415
X(stmts) \
@@ -43,7 +44,6 @@
4344
X(try) \
4445
X(catches) \
4546
X(finally) \
46-
X(type) \
4747
X(init) \
4848
X(loop) \
4949

php_ast.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ ZEND_EXTERN_MODULE_GLOBALS(ast)
3737
#define AST_STR(str) AST_G(str)
3838

3939
/* Custom ast kind for names */
40-
#define AST_NAME 2048
41-
#define AST_CLOSURE_VAR 2049
40+
#define AST_NAME 2048
41+
#define AST_CLOSURE_VAR 2049
42+
#define AST_NULLABLE_TYPE 2050
4243

4344
/* Pretend it still exists */
4445
#if PHP_VERSION_ID >= 70100

scripts/generate_ast_data.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
/* special nodes */
5252
'AST_NAME' => ['name'],
5353
'AST_CLOSURE_VAR' => ['name'],
54+
'AST_NULLABLE_TYPE' => ['type'],
5455

5556
/* declaration nodes */
5657
'ZEND_AST_FUNC_DECL' => $funcNames,

tests/nullable_types.phpt

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
--TEST--
2+
Nullable types
3+
--SKIPIF--
4+
<?php if (PHP_VERSION_ID < 70100) die('skip PHP >= 7.1 only'); ?>
5+
--FILE--
6+
<?php
7+
8+
require __DIR__ . '/../util.php';
9+
10+
$code = <<<'PHP'
11+
<?php
12+
function test(?Foo $foo) : ?Bar {
13+
}
14+
function test(?int $foo) : ?int {
15+
}
16+
PHP;
17+
18+
echo ast_dump(ast\parse_code($code, $version=40));
19+
20+
?>
21+
--EXPECT--
22+
AST_STMT_LIST
23+
0: AST_FUNC_DECL
24+
flags: 0
25+
name: test
26+
params: AST_PARAM_LIST
27+
0: AST_PARAM
28+
flags: 0
29+
type: AST_NULLABLE_TYPE
30+
type: AST_NAME
31+
flags: NAME_NOT_FQ (1)
32+
name: "Foo"
33+
name: "foo"
34+
default: null
35+
uses: null
36+
stmts: AST_STMT_LIST
37+
returnType: AST_NULLABLE_TYPE
38+
type: AST_NAME
39+
flags: NAME_NOT_FQ (1)
40+
name: "Bar"
41+
1: AST_FUNC_DECL
42+
flags: 0
43+
name: test
44+
params: AST_PARAM_LIST
45+
0: AST_PARAM
46+
flags: 0
47+
type: AST_NULLABLE_TYPE
48+
type: AST_TYPE
49+
flags: TYPE_LONG (4)
50+
name: "foo"
51+
default: null
52+
uses: null
53+
stmts: AST_STMT_LIST
54+
returnType: AST_NULLABLE_TYPE
55+
type: AST_TYPE
56+
flags: TYPE_LONG (4)

0 commit comments

Comments
 (0)