Skip to content

Commit 2040325

Browse files
committed
Support PHP 8.0 Union Type RFC
1 parent b062464 commit 2040325

File tree

9 files changed

+272
-11
lines changed

9 files changed

+272
-11
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ AST_STMT_LIST
454454
AST_SWITCH_LIST
455455
AST_TRAIT_ADAPTATIONS
456456
AST_USE
457+
AST_TYPE_UNION // php 8.0+ union types
457458
```
458459

459460
AST Versioning

ast.c

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -348,13 +348,21 @@ static inline zend_bool ast_kind_is_decl(zend_ast_kind kind) {
348348
}
349349

350350
static inline zend_bool ast_is_name(zend_ast *ast, zend_ast *parent, uint32_t i) {
351-
if (!ast || ast->kind != ZEND_AST_ZVAL || Z_TYPE_P(zend_ast_get_zval(ast)) != IS_STRING) {
351+
if (!ast) {
352+
return 0;
353+
}
354+
if (ast->kind != ZEND_AST_ZVAL || Z_TYPE_P(zend_ast_get_zval(ast)) != IS_STRING) {
352355
return 0;
353356
}
354357

355358
if (parent->kind == ZEND_AST_NAME_LIST) {
356359
return 1;
357360
}
361+
#if PHP_VERSION_ID >= 80000
362+
if (parent->kind == ZEND_AST_TYPE_UNION) {
363+
return 1;
364+
}
365+
#endif
358366

359367
if (i == 0) {
360368
return parent->kind == ZEND_AST_CATCH || parent->kind == ZEND_AST_CLASS
@@ -383,6 +391,11 @@ static inline zend_bool ast_is_name(zend_ast *ast, zend_ast *parent, uint32_t i)
383391

384392
/* Assumes that ast_is_name is already true */
385393
static inline zend_bool ast_is_type(zend_ast *ast, zend_ast *parent, uint32_t i) {
394+
#if PHP_VERSION_ID >= 80000
395+
if (parent->kind == ZEND_AST_TYPE_UNION) {
396+
return 1;
397+
}
398+
#endif
386399
if (i == 0) {
387400
return parent->kind == ZEND_AST_PARAM
388401
#if PHP_VERSION_ID >= 70400
@@ -444,6 +457,8 @@ static const builtin_type_info builtin_types[] = {
444457
{ZEND_STRL("void"), IS_VOID},
445458
{ZEND_STRL("iterable"), IS_ITERABLE},
446459
{ZEND_STRL("object"), IS_OBJECT},
460+
{ZEND_STRL("null"), IS_NULL}, /* Null and false for php 8.0 union types */
461+
{ZEND_STRL("false"), IS_FALSE},
447462
{NULL, 0, IS_UNDEF}
448463
};
449464
static inline zend_uchar lookup_builtin_type(const zend_string *name) {
@@ -579,12 +594,13 @@ static void ast_fill_children_ht(HashTable *ht, zend_ast *ast, ast_state_info_t
579594
uint32_t i, count;
580595
zend_bool is_list = zend_ast_is_list(ast);
581596
zend_ast **children = ast_get_children(ast, &count);
597+
const zend_ast_kind ast_kind = ast->kind;
582598
for (i = 0; i < count; ++i) {
583599
zend_ast *child = children[i];
584-
zend_string *child_name = !is_list ? ast_kind_child_name(ast->kind, i) : NULL;
600+
zend_string *child_name = !is_list ? ast_kind_child_name(ast_kind, i) : NULL;
585601
zval child_zv;
586602

587-
if (ast->kind == ZEND_AST_STMT_LIST) {
603+
if (ast_kind == ZEND_AST_STMT_LIST) {
588604
if (child != NULL && child->kind == ZEND_AST_STMT_LIST) {
589605
ast_fill_children_ht(ht, child, state);
590606
continue;
@@ -596,7 +612,7 @@ static void ast_fill_children_ht(HashTable *ht, zend_ast *ast, ast_state_info_t
596612

597613
/* This AST_CATCH check should occur before ast_is_name() */
598614
#if PHP_VERSION_ID < 70100
599-
if (ast->kind == ZEND_AST_CATCH && i == 0) {
615+
if (ast_kind == ZEND_AST_CATCH && i == 0) {
600616
/* Emulate PHP 7.1 format (name list) */
601617
zval tmp;
602618
ast_create_virtual_node(&tmp, AST_NAME, child->attr, child, state);
@@ -609,7 +625,7 @@ static void ast_fill_children_ht(HashTable *ht, zend_ast *ast, ast_state_info_t
609625
} else if (child && child->kind == ZEND_AST_TYPE && (child->attr & ZEND_TYPE_NULLABLE)) {
610626
child->attr &= ~ZEND_TYPE_NULLABLE;
611627
ast_create_virtual_node(&child_zv, AST_NULLABLE_TYPE, 0, child, state);
612-
} else if (ast->kind == ZEND_AST_CLOSURE_USES) {
628+
} else if (ast_kind == ZEND_AST_CLOSURE_USES) {
613629
ast_create_virtual_node(&child_zv, AST_CLOSURE_VAR, child->attr, child, state);
614630
} else if (ast_is_var_name(child, ast, i)) {
615631
ast_create_virtual_node(&child_zv, ZEND_AST_VAR, 0, child, state);
@@ -624,20 +640,20 @@ static void ast_fill_children_ht(HashTable *ht, zend_ast *ast, ast_state_info_t
624640
&child_zv, ZEND_AST_STMT_LIST, 0, zend_ast_get_lineno(ast), state, 0);
625641
}
626642
} else if (state->version >= 60 && i == 1
627-
&& (ast->kind == ZEND_AST_FUNC_DECL || ast->kind == ZEND_AST_METHOD)) {
643+
&& (ast_kind == ZEND_AST_FUNC_DECL || ast_kind == ZEND_AST_METHOD)) {
628644
/* Skip "uses" child, it is only relevant for closures */
629645
continue;
630646
#if PHP_VERSION_ID >= 70400
631-
} else if (i == 1 && ast->kind == ZEND_AST_ARROW_FUNC) {
647+
} else if (i == 1 && ast_kind == ZEND_AST_ARROW_FUNC) {
632648
/* Skip "uses" child since it is always empty */
633649
continue;
634650
#endif
635651
#if PHP_VERSION_ID >= 70100
636-
} else if (ast->kind == ZEND_AST_LIST && child != NULL) {
652+
} else if (ast_kind == ZEND_AST_LIST && child != NULL) {
637653
/* Emulate simple variable list */
638654
ast_to_zval(&child_zv, child->child[0], state);
639655
#else
640-
} else if (ast->kind == ZEND_AST_ARRAY
656+
} else if (ast_kind == ZEND_AST_ARRAY
641657
&& ast->attr == ZEND_ARRAY_SYNTAX_LIST && child != NULL) {
642658
/* Emulate ARRAY_ELEM list */
643659
zval ch0, ch1;
@@ -661,14 +677,14 @@ static void ast_fill_children_ht(HashTable *ht, zend_ast *ast, ast_state_info_t
661677

662678
#if PHP_VERSION_ID < 70100
663679
/* Emulate docComment on constants, which is not available in PHP 7.0 */
664-
if (state->version >= 60 && ast->kind == ZEND_AST_CONST_ELEM) {
680+
if (state->version >= 60 && ast_kind == ZEND_AST_CONST_ELEM) {
665681
zval tmp;
666682
ZVAL_NULL(&tmp);
667683
zend_hash_add_new(ht, AST_STR(str_docComment), &tmp);
668684
}
669685
#endif
670686

671-
if (ast_kind_is_decl(ast->kind)) {
687+
if (ast_kind_is_decl(ast_kind)) {
672688
zval id_zval;
673689
ZVAL_LONG(&id_zval, state->declIdCounter);
674690
state->declIdCounter++;

ast_data.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const zend_ast_kind ast_kinds[] = {
1818
ZEND_AST_NAME_LIST,
1919
ZEND_AST_TRAIT_ADAPTATIONS,
2020
ZEND_AST_USE,
21+
ZEND_AST_TYPE_UNION,
2122
AST_NAME,
2223
AST_CLOSURE_VAR,
2324
AST_NULLABLE_TYPE,
@@ -118,6 +119,7 @@ const char *ast_kind_to_name(zend_ast_kind kind) {
118119
case ZEND_AST_NAME_LIST: return "AST_NAME_LIST";
119120
case ZEND_AST_TRAIT_ADAPTATIONS: return "AST_TRAIT_ADAPTATIONS";
120121
case ZEND_AST_USE: return "AST_USE";
122+
case ZEND_AST_TYPE_UNION: return "AST_TYPE_UNION";
121123
case AST_NAME: return "AST_NAME";
122124
case AST_CLOSURE_VAR: return "AST_CLOSURE_VAR";
123125
case AST_NULLABLE_TYPE: return "AST_NULLABLE_TYPE";
@@ -668,6 +670,7 @@ void ast_register_kind_constants(INIT_FUNC_ARGS) {
668670
REGISTER_NS_LONG_CONSTANT("ast", "AST_NAME_LIST", ZEND_AST_NAME_LIST, CONST_CS | CONST_PERSISTENT);
669671
REGISTER_NS_LONG_CONSTANT("ast", "AST_TRAIT_ADAPTATIONS", ZEND_AST_TRAIT_ADAPTATIONS, CONST_CS | CONST_PERSISTENT);
670672
REGISTER_NS_LONG_CONSTANT("ast", "AST_USE", ZEND_AST_USE, CONST_CS | CONST_PERSISTENT);
673+
REGISTER_NS_LONG_CONSTANT("ast", "AST_TYPE_UNION", ZEND_AST_TYPE_UNION, CONST_CS | CONST_PERSISTENT);
671674
REGISTER_NS_LONG_CONSTANT("ast", "AST_NAME", AST_NAME, CONST_CS | CONST_PERSISTENT);
672675
REGISTER_NS_LONG_CONSTANT("ast", "AST_CLOSURE_VAR", AST_CLOSURE_VAR, CONST_CS | CONST_PERSISTENT);
673676
REGISTER_NS_LONG_CONSTANT("ast", "AST_NULLABLE_TYPE", AST_NULLABLE_TYPE, CONST_CS | CONST_PERSISTENT);

package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
</stability>
2424
<license uri="https://github.com/nikic/php-ast/blob/master/LICENSE">BSD-3-Clause</license>
2525
<notes>
26+
- Add AST_TYPE_UNION to support PHP 8.0 Union Types.
2627
- TBD.
2728
</notes>
2829
<contents>

php_ast.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ extern ast_str_globals str_globals;
5757
# define ZEND_AST_ARROW_FUNC 0x5ff
5858
#endif
5959

60+
#if PHP_VERSION_ID < 80000
61+
/* NOTE: For list nodes, the first set bit is 0x80 */
62+
# define ZEND_AST_TYPE_UNION ((1 << (ZEND_AST_IS_LIST_SHIFT + 1)) - 2)
63+
#endif
64+
6065
/* Pretend it still exists */
6166
#if PHP_VERSION_ID >= 70100
6267
# define ZEND_AST_LIST ((1 << (ZEND_AST_IS_LIST_SHIFT + 1)) - 1)

scripts/generate_ast_data.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@
161161
'ZEND_AST_NAME_LIST',
162162
'ZEND_AST_TRAIT_ADAPTATIONS',
163163
'ZEND_AST_USE',
164+
'ZEND_AST_TYPE_UNION',
164165
];
165166

166167
$data = [];

tests/metadata.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ AST_CLASS_CONST_DECL: (combinable) [MODIFIER_PUBLIC, MODIFIER_PROTECTED, MODIFIE
4141
AST_NAME_LIST: []
4242
AST_TRAIT_ADAPTATIONS: []
4343
AST_USE: [USE_NORMAL, USE_FUNCTION, USE_CONST]
44+
AST_TYPE_UNION: []
4445
AST_NAME: [NAME_FQ, NAME_NOT_FQ, NAME_RELATIVE]
4546
AST_CLOSURE_VAR: [CLOSURE_USE_REF]
4647
AST_NULLABLE_TYPE: []

tests/php80_union_types.phpt

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
--TEST--
2+
Union types in PHP 8.0
3+
--SKIPIF--
4+
<?php if (PHP_VERSION_ID < 80000) die('skip PHP >= 8.0 only'); ?>
5+
--FILE--
6+
<?php
7+
8+
require __DIR__ . '/../util.php';
9+
10+
$code = <<<'PHP'
11+
<?php
12+
namespace NS;
13+
function test(OBJECT|array|float|int|String|null $a) : string|false {
14+
return json_encode($a);
15+
}
16+
class Xyz {
17+
public bool|stdClass $x;
18+
}
19+
function testClasses(iterable|\stdClass|Xyz $s) : namespace\Xyz|false|null {
20+
return new X();
21+
}
22+
test([]);
23+
testClasses([2,3]);
24+
PHP;
25+
26+
$node = ast\parse_code($code, $version=70);
27+
echo ast_dump($node), "\n";
28+
--EXPECTF--
29+
AST_STMT_LIST
30+
0: AST_NAMESPACE
31+
name: "NS"
32+
stmts: null
33+
1: AST_FUNC_DECL
34+
flags: 0
35+
name: "test"
36+
docComment: null
37+
params: AST_PARAM_LIST
38+
0: AST_PARAM
39+
flags: 0
40+
type: AST_TYPE_UNION
41+
0: AST_TYPE
42+
flags: TYPE_OBJECT (%d)
43+
1: AST_TYPE
44+
flags: TYPE_ARRAY (%d)
45+
2: AST_TYPE
46+
flags: TYPE_DOUBLE (%d)
47+
3: AST_TYPE
48+
flags: TYPE_LONG (%d)
49+
4: AST_TYPE
50+
flags: TYPE_STRING (%d)
51+
5: AST_TYPE
52+
flags: TYPE_NULL (%d)
53+
name: "a"
54+
default: null
55+
stmts: AST_STMT_LIST
56+
0: AST_RETURN
57+
expr: AST_CALL
58+
expr: AST_NAME
59+
flags: NAME_NOT_FQ (1)
60+
name: "json_encode"
61+
args: AST_ARG_LIST
62+
0: AST_VAR
63+
name: "a"
64+
returnType: AST_TYPE_UNION
65+
0: AST_TYPE
66+
flags: TYPE_STRING (%d)
67+
1: AST_TYPE
68+
flags: 2
69+
__declId: 0
70+
2: AST_CLASS
71+
flags: 0
72+
name: "Xyz"
73+
docComment: null
74+
extends: null
75+
implements: null
76+
stmts: AST_STMT_LIST
77+
0: AST_PROP_GROUP
78+
flags: MODIFIER_PUBLIC (1)
79+
type: AST_TYPE_UNION
80+
0: AST_TYPE
81+
flags: TYPE_BOOL (%d)
82+
1: AST_NAME
83+
flags: NAME_NOT_FQ (1)
84+
name: "stdClass"
85+
props: AST_PROP_DECL
86+
flags: 0
87+
0: AST_PROP_ELEM
88+
name: "x"
89+
default: null
90+
docComment: null
91+
__declId: 1
92+
3: AST_FUNC_DECL
93+
flags: 0
94+
name: "testClasses"
95+
docComment: null
96+
params: AST_PARAM_LIST
97+
0: AST_PARAM
98+
flags: 0
99+
type: AST_TYPE_UNION
100+
0: AST_TYPE
101+
flags: TYPE_ITERABLE (%d)
102+
1: AST_NAME
103+
flags: NAME_FQ (0)
104+
name: "stdClass"
105+
2: AST_NAME
106+
flags: NAME_NOT_FQ (1)
107+
name: "Xyz"
108+
name: "s"
109+
default: null
110+
stmts: AST_STMT_LIST
111+
0: AST_RETURN
112+
expr: AST_NEW
113+
class: AST_NAME
114+
flags: NAME_NOT_FQ (1)
115+
name: "X"
116+
args: AST_ARG_LIST
117+
returnType: AST_TYPE_UNION
118+
0: AST_NAME
119+
flags: NAME_RELATIVE (2)
120+
name: "Xyz"
121+
1: AST_TYPE
122+
flags: 2
123+
2: AST_TYPE
124+
flags: TYPE_NULL (%d)
125+
__declId: 2
126+
4: AST_CALL
127+
expr: AST_NAME
128+
flags: NAME_NOT_FQ (1)
129+
name: "test"
130+
args: AST_ARG_LIST
131+
0: AST_ARRAY
132+
flags: ARRAY_SYNTAX_SHORT (3)
133+
5: AST_CALL
134+
expr: AST_NAME
135+
flags: NAME_NOT_FQ (1)
136+
name: "testClasses"
137+
args: AST_ARG_LIST
138+
0: AST_ARRAY
139+
flags: ARRAY_SYNTAX_SHORT (3)
140+
0: AST_ARRAY_ELEM
141+
flags: 0
142+
value: 2
143+
key: null
144+
1: AST_ARRAY_ELEM
145+
flags: 0
146+
value: 3
147+
key: null

0 commit comments

Comments
 (0)