Skip to content

Commit 83c769f

Browse files
authored
Support php 8.1 intersection types (#208)
* Add tests of final class constant modifier, update metadata (https://wiki.php.net/rfc/final_class_const for php 8.1) * Support proposed php 8.1 intersection types (https://wiki.php.net/rfc/pure-intersection-types) * Allow using any modifier on any class element in metadata
1 parent dafc341 commit 83c769f

File tree

10 files changed

+219
-25
lines changed

10 files changed

+219
-25
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,8 @@ ast\flags\NAME_FQ (= 0) // example: \Foo\Bar
227227
ast\flags\NAME_NOT_FQ // example: Foo\Bar
228228
ast\flags\NAME_RELATIVE // example: namespace\Foo\Bar
229229
230-
// Used by ast\AST_METHOD, ast\AST_PROP_DECL, ast\AST_CLASS_CONST_DECL,
231-
// ast\AST_PROP_GROUP and ast\AST_TRAIT_ALIAS (combinable)
230+
// Used by ast\AST_METHOD, ast\AST_PROP_DECL, ast\AST_PROP_GROUP,
231+
// ast\AST_CLASS_CONST_DECL, ast\AST_CLASS_CONST_GROUP, and ast\AST_TRAIT_ALIAS (combinable)
232232
ast\flags\MODIFIER_PUBLIC
233233
ast\flags\MODIFIER_PROTECTED
234234
ast\flags\MODIFIER_PRIVATE
@@ -475,6 +475,7 @@ AST_SWITCH_LIST
475475
AST_TRAIT_ADAPTATIONS
476476
AST_USE
477477
AST_TYPE_UNION // php 8.0+ union types
478+
AST_TYPE_INTERSECTION // php 8.1+ intersection types
478479
```
479480

480481
AST Versioning

ast.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,8 @@ static const ast_flag_info flag_info[] = {
321321
{ ZEND_AST_ARROW_FUNC, 1, func_flags },
322322
{ ZEND_AST_PROP_DECL, 1, modifier_flags },
323323
{ ZEND_AST_PROP_GROUP, 1, modifier_flags },
324-
{ ZEND_AST_CLASS_CONST_DECL, 1, visibility_flags },
325-
{ ZEND_AST_CLASS_CONST_GROUP, 1, visibility_flags },
324+
{ ZEND_AST_CLASS_CONST_DECL, 1, modifier_flags },
325+
{ ZEND_AST_CLASS_CONST_GROUP, 1, modifier_flags },
326326
{ ZEND_AST_TRAIT_ALIAS, 1, modifier_flags },
327327
{ ZEND_AST_DIM, 1, dim_flags },
328328
{ ZEND_AST_CONDITIONAL, 1, conditional_flags },
@@ -423,6 +423,11 @@ static inline zend_bool ast_is_name(zend_ast *ast, zend_ast *parent, uint32_t i)
423423
if (parent->kind == ZEND_AST_NAME_LIST) {
424424
return 1;
425425
}
426+
#if PHP_VERSION_ID >= 80100
427+
if (parent->kind == ZEND_AST_TYPE_INTERSECTION) {
428+
return 1;
429+
}
430+
#endif
426431
#if PHP_VERSION_ID >= 80000
427432
if (parent->kind == ZEND_AST_TYPE_UNION) {
428433
return 1;
@@ -465,6 +470,11 @@ static inline zend_bool ast_is_name(zend_ast *ast, zend_ast *parent, uint32_t i)
465470

466471
/* Assumes that ast_is_name is already true */
467472
static inline zend_bool ast_is_type(zend_ast *ast, zend_ast *parent, uint32_t i) {
473+
#if PHP_VERSION_ID >= 80100
474+
if (parent->kind == ZEND_AST_TYPE_INTERSECTION) {
475+
return 1;
476+
}
477+
#endif
468478
#if PHP_VERSION_ID >= 80000
469479
if (parent->kind == ZEND_AST_TYPE_UNION) {
470480
return 1;

ast_data.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const zend_ast_kind ast_kinds[] = {
1919
ZEND_AST_TRAIT_ADAPTATIONS,
2020
ZEND_AST_USE,
2121
ZEND_AST_TYPE_UNION,
22+
ZEND_AST_TYPE_INTERSECTION,
2223
ZEND_AST_ATTRIBUTE_LIST,
2324
ZEND_AST_ATTRIBUTE_GROUP,
2425
ZEND_AST_MATCH_ARM_LIST,
@@ -131,6 +132,7 @@ const char *ast_kind_to_name(zend_ast_kind kind) {
131132
case ZEND_AST_TRAIT_ADAPTATIONS: return "AST_TRAIT_ADAPTATIONS";
132133
case ZEND_AST_USE: return "AST_USE";
133134
case ZEND_AST_TYPE_UNION: return "AST_TYPE_UNION";
135+
case ZEND_AST_TYPE_INTERSECTION: return "AST_TYPE_INTERSECTION";
134136
case ZEND_AST_ATTRIBUTE_LIST: return "AST_ATTRIBUTE_LIST";
135137
case ZEND_AST_ATTRIBUTE_GROUP: return "AST_ATTRIBUTE_GROUP";
136138
case ZEND_AST_MATCH_ARM_LIST: return "AST_MATCH_ARM_LIST";
@@ -581,6 +583,7 @@ void ast_register_kind_constants(INIT_FUNC_ARGS) {
581583
REGISTER_NS_LONG_CONSTANT("ast", "AST_TRAIT_ADAPTATIONS", ZEND_AST_TRAIT_ADAPTATIONS, CONST_CS | CONST_PERSISTENT);
582584
REGISTER_NS_LONG_CONSTANT("ast", "AST_USE", ZEND_AST_USE, CONST_CS | CONST_PERSISTENT);
583585
REGISTER_NS_LONG_CONSTANT("ast", "AST_TYPE_UNION", ZEND_AST_TYPE_UNION, CONST_CS | CONST_PERSISTENT);
586+
REGISTER_NS_LONG_CONSTANT("ast", "AST_TYPE_INTERSECTION", ZEND_AST_TYPE_INTERSECTION, CONST_CS | CONST_PERSISTENT);
584587
REGISTER_NS_LONG_CONSTANT("ast", "AST_ATTRIBUTE_LIST", ZEND_AST_ATTRIBUTE_LIST, CONST_CS | CONST_PERSISTENT);
585588
REGISTER_NS_LONG_CONSTANT("ast", "AST_ATTRIBUTE_GROUP", ZEND_AST_ATTRIBUTE_GROUP, CONST_CS | CONST_PERSISTENT);
586589
REGISTER_NS_LONG_CONSTANT("ast", "AST_MATCH_ARM_LIST", ZEND_AST_MATCH_ARM_LIST, CONST_CS | CONST_PERSISTENT);

ast_stub.php

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@
2525
const AST_TRAIT_ADAPTATIONS = 142;
2626
const AST_USE = 143;
2727
const AST_TYPE_UNION = 144;
28-
const AST_ATTRIBUTE_LIST = 145;
29-
const AST_ATTRIBUTE_GROUP = 146;
30-
const AST_MATCH_ARM_LIST = 147;
28+
const AST_TYPE_INTERSECTION = 145;
29+
const AST_ATTRIBUTE_LIST = 146;
30+
const AST_ATTRIBUTE_GROUP = 147;
31+
const AST_MATCH_ARM_LIST = 148;
3132
const AST_NAME = 2048;
3233
const AST_CLOSURE_VAR = 2049;
3334
const AST_NULLABLE_TYPE = 2050;
@@ -194,14 +195,14 @@
194195
const USE_NORMAL = 1;
195196
const USE_FUNCTION = 2;
196197
const USE_CONST = 4;
197-
const MAGIC_LINE = 376;
198-
const MAGIC_FILE = 377;
199-
const MAGIC_DIR = 378;
200-
const MAGIC_NAMESPACE = 383;
201-
const MAGIC_FUNCTION = 382;
202-
const MAGIC_METHOD = 381;
203-
const MAGIC_CLASS = 379;
204-
const MAGIC_TRAIT = 380;
198+
const MAGIC_LINE = 379;
199+
const MAGIC_FILE = 380;
200+
const MAGIC_DIR = 381;
201+
const MAGIC_NAMESPACE = 386;
202+
const MAGIC_FUNCTION = 385;
203+
const MAGIC_METHOD = 384;
204+
const MAGIC_CLASS = 382;
205+
const MAGIC_TRAIT = 383;
205206
const ARRAY_SYNTAX_LIST = 1;
206207
const ARRAY_SYNTAX_LONG = 2;
207208
const ARRAY_SYNTAX_SHORT = 3;

package.xml

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,18 @@
2020
</lead>
2121
<date>2021-05-16</date>
2222
<version>
23-
<release>1.0.12</release>
24-
<api>1.0.12</api>
23+
<release>1.0.13dev</release>
24+
<api>1.0.13dev</api>
2525
</version>
2626
<stability>
27-
<release>stable</release>
28-
<api>stable</api>
27+
<release>alpha</release>
28+
<api>alpha</api>
2929
</stability>
3030
<license uri="https://github.com/nikic/php-ast/blob/master/LICENSE">BSD-3-Clause</license>
3131
<notes>
32-
- Support parsing 'docComment' on php 8.1 enums
32+
- Indicate in metadata that 'AST_CLASS_CONST_GROUP' and 'AST_CLASS_CONST_DECL' can have MODIFIER_FINAL
33+
(and any modifier a class element can have, to reflect what the parser can actually parse)
34+
- Support php 8.1 intersection types, add node kind 'AST_TYPE_INTERSECTION'
3335
</notes>
3436
<contents>
3537
<dir name="/">
@@ -111,6 +113,8 @@
111113
<file name="php80_union_types_nullable.phpt" role="test" />
112114
<file name="php80_union_types.phpt" role="test" />
113115
<file name="php81_enums.phpt" role="test" />
116+
<file name="php81_final_class_const.phpt" role="test" />
117+
<file name="php81_intersection_types.phpt" role="test" />
114118
<file name="prop_doc_comments.phpt" role="test" />
115119
<file name="short_arrow_function.phpt" role="test" />
116120
<file name="short_arrow_function_return.phpt" role="test" />
@@ -138,6 +142,22 @@
138142
<providesextension>ast</providesextension>
139143
<extsrcrelease />
140144
<changelog>
145+
<release>
146+
<date>2021-05-16</date>
147+
<version>
148+
<release>1.0.12</release>
149+
<api>1.0.12</api>
150+
</version>
151+
<stability>
152+
<release>stable</release>
153+
<api>stable</api>
154+
</stability>
155+
<license uri="https://github.com/nikic/php-ast/blob/master/LICENSE">BSD-3-Clause</license>
156+
<notes>
157+
- Support parsing 'docComment' on php 8.1 enums
158+
- Indicate in metadata that 'AST_CLASS_CONST_GROUP' and 'AST_CLASS_CONST_DECL' can have MODIFIER_FINAL
159+
</notes>
160+
</release>
141161
<release>
142162
<date>2021-04-20</date>
143163
<version>

php_ast.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
extern zend_module_entry ast_module_entry;
88
#define phpext_ast_ptr &ast_module_entry
99

10-
#define PHP_AST_VERSION "1.0.12"
10+
#define PHP_AST_VERSION "1.0.13dev"
1111

1212
#ifdef PHP_WIN32
1313
# define PHP_AST_API __declspec(dllexport)
@@ -79,6 +79,7 @@ extern ast_str_globals str_globals;
7979
# define ZEND_ACC_ENUM (1 << 22)
8080
/* 3 child nodes - name, expr, attributes */
8181
# define ZEND_AST_ENUM_CASE 0x3fe
82+
# define ZEND_AST_TYPE_INTERSECTION ((1 << (ZEND_AST_IS_LIST_SHIFT + 1)) - 6)
8283
#endif
8384

8485
/* Pretend it still exists */

scripts/generate_ast_data.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@
172172
'ZEND_AST_TRAIT_ADAPTATIONS',
173173
'ZEND_AST_USE',
174174
'ZEND_AST_TYPE_UNION',
175+
'ZEND_AST_TYPE_INTERSECTION',
175176
'ZEND_AST_ATTRIBUTE_LIST',
176177
'ZEND_AST_ATTRIBUTE_GROUP',
177178
'ZEND_AST_MATCH_ARM_LIST',

tests/metadata.phpt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ foreach ($metadata as $data) {
2525
// NOTE: AST_PARAM has overlapping flag values for MODIFIER_PUBLIC and PARAM_REF in php 7.4.
2626
// To work around this, MODIFIER_* were omitted from get_metadata in 7.4 and older.
2727
?>
28-
--EXPECTF--
28+
--EXPECT--
2929
AST_ARG_LIST: []
3030
AST_LIST: []
3131
AST_ARRAY: [ARRAY_SYNTAX_LIST, ARRAY_SYNTAX_LONG, ARRAY_SYNTAX_SHORT]
@@ -39,11 +39,12 @@ AST_PARAM_LIST: []
3939
AST_CLOSURE_USES: []
4040
AST_PROP_DECL: (combinable) [MODIFIER_PUBLIC, MODIFIER_PROTECTED, MODIFIER_PRIVATE, MODIFIER_STATIC, MODIFIER_ABSTRACT, MODIFIER_FINAL]
4141
AST_CONST_DECL: []
42-
AST_CLASS_CONST_DECL: (combinable) [MODIFIER_PUBLIC, MODIFIER_PROTECTED, MODIFIER_PRIVATE]
42+
AST_CLASS_CONST_DECL: (combinable) [MODIFIER_PUBLIC, MODIFIER_PROTECTED, MODIFIER_PRIVATE, MODIFIER_STATIC, MODIFIER_ABSTRACT, MODIFIER_FINAL]
4343
AST_NAME_LIST: []
4444
AST_TRAIT_ADAPTATIONS: []
4545
AST_USE: [USE_NORMAL, USE_FUNCTION, USE_CONST]
4646
AST_TYPE_UNION: []
47+
AST_TYPE_INTERSECTION: []
4748
AST_ATTRIBUTE_LIST: []
4849
AST_ATTRIBUTE_GROUP: []
4950
AST_MATCH_ARM_LIST: []
@@ -86,7 +87,7 @@ AST_GOTO: []
8687
AST_BREAK: []
8788
AST_CONTINUE: []
8889
AST_CLASS_NAME: []
89-
AST_CLASS_CONST_GROUP: (combinable) [MODIFIER_PUBLIC, MODIFIER_PROTECTED, MODIFIER_PRIVATE]
90+
AST_CLASS_CONST_GROUP: (combinable) [MODIFIER_PUBLIC, MODIFIER_PROTECTED, MODIFIER_PRIVATE, MODIFIER_STATIC, MODIFIER_ABSTRACT, MODIFIER_FINAL]
9091
AST_DIM: (combinable) [DIM_ALTERNATIVE_SYNTAX]
9192
AST_PROP: []
9293
AST_NULLSAFE_PROP: []
@@ -131,4 +132,4 @@ AST_CATCH: []
131132
AST_FOR: []
132133
AST_FOREACH: []
133134
AST_ENUM_CASE: []
134-
AST_PARAM: (combinable) [PARAM_REF, PARAM_VARIADIC, PARAM_MODIFIER_PUBLIC, PARAM_MODIFIER_PROTECTED, PARAM_MODIFIER_PRIVATE]
135+
AST_PARAM: (combinable) [PARAM_REF, PARAM_VARIADIC, PARAM_MODIFIER_PUBLIC, PARAM_MODIFIER_PROTECTED, PARAM_MODIFIER_PRIVATE]

tests/php81_final_class_const.phpt

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
--TEST--
2+
Final class constants in php 8.1
3+
--SKIPIF--
4+
<?php if (PHP_VERSION_ID < 70100) { echo "skip parsing class const modifiers requires php 7.1+\n"; } ?>
5+
--FILE--
6+
<?php
7+
8+
require __DIR__ . '/../util.php';
9+
10+
// In older php versions, this is allowed by the parser but forbidden by the compiler.
11+
$code = <<<'PHP'
12+
<?php
13+
class X {
14+
final private const Y = 1;
15+
}
16+
PHP;
17+
18+
$node = ast\parse_code($code, $version=70);
19+
echo ast_dump($node), "\n";
20+
$node = ast\parse_code($code, $version=80);
21+
echo ast_dump($node), "\n";
22+
--EXPECTF--
23+
AST_STMT_LIST
24+
0: AST_CLASS
25+
flags: 0
26+
name: "X"
27+
docComment: null
28+
extends: null
29+
implements: null
30+
stmts: AST_STMT_LIST
31+
0: AST_CLASS_CONST_DECL
32+
flags: MODIFIER_PRIVATE | MODIFIER_FINAL (%d)
33+
0: AST_CONST_ELEM
34+
name: "Y"
35+
value: 1
36+
docComment: null
37+
__declId: 0
38+
AST_STMT_LIST
39+
0: AST_CLASS
40+
flags: 0
41+
name: "X"
42+
docComment: null
43+
extends: null
44+
implements: null
45+
stmts: AST_STMT_LIST
46+
0: AST_CLASS_CONST_GROUP
47+
flags: MODIFIER_PRIVATE | MODIFIER_FINAL (%d)
48+
const: AST_CLASS_CONST_DECL
49+
flags: 0
50+
0: AST_CONST_ELEM
51+
name: "Y"
52+
value: 1
53+
docComment: null
54+
attributes: null
55+
attributes: null
56+
__declId: 0

tests/php81_intersection_types.phpt

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
--TEST--
2+
Intersection types in php 8.1
3+
--SKIPIF--
4+
<?php if (PHP_VERSION_ID < 80100) die('skip PHP >= 8.1 only'); ?>
5+
--FILE--
6+
<?php
7+
8+
require __DIR__ . '/../util.php';
9+
10+
$code = <<<'PHP'
11+
<?php
12+
class X {
13+
public Countable&ArrayAccess&Traversable $arrayLike;
14+
public function example(Throwable&Countable $tc): self&Countable {
15+
throw $tc;
16+
}
17+
}
18+
// Using iterable, int, etc are compilation errors, not parse errors - programs using the output of php-ast will have to check for unsupported primitive types
19+
// (Fatal error: Type int cannot be part of an intersection type)
20+
function this_is_a_compile_error(): iterable&Countable {}
21+
PHP;
22+
23+
$node = ast\parse_code($code, $version=80);
24+
echo ast_dump($node), "\n";
25+
26+
--EXPECTF--
27+
AST_STMT_LIST
28+
0: AST_CLASS
29+
flags: 0
30+
name: "X"
31+
docComment: null
32+
extends: null
33+
implements: null
34+
stmts: AST_STMT_LIST
35+
0: AST_PROP_GROUP
36+
flags: MODIFIER_PUBLIC (%d)
37+
type: AST_TYPE_INTERSECTION
38+
0: AST_NAME
39+
flags: NAME_NOT_FQ (%d)
40+
name: "Countable"
41+
1: AST_NAME
42+
flags: NAME_NOT_FQ (%d)
43+
name: "ArrayAccess"
44+
2: AST_NAME
45+
flags: NAME_NOT_FQ (%d)
46+
name: "Traversable"
47+
props: AST_PROP_DECL
48+
flags: 0
49+
0: AST_PROP_ELEM
50+
name: "arrayLike"
51+
default: null
52+
docComment: null
53+
attributes: null
54+
1: AST_METHOD
55+
flags: MODIFIER_PUBLIC (%d)
56+
name: "example"
57+
docComment: null
58+
params: AST_PARAM_LIST
59+
0: AST_PARAM
60+
flags: 0
61+
type: AST_TYPE_INTERSECTION
62+
0: AST_NAME
63+
flags: NAME_NOT_FQ (%d)
64+
name: "Throwable"
65+
1: AST_NAME
66+
flags: NAME_NOT_FQ (%d)
67+
name: "Countable"
68+
name: "tc"
69+
default: null
70+
attributes: null
71+
docComment: null
72+
stmts: AST_STMT_LIST
73+
0: AST_THROW
74+
expr: AST_VAR
75+
name: "tc"
76+
returnType: AST_TYPE_INTERSECTION
77+
0: AST_NAME
78+
flags: NAME_NOT_FQ (%d)
79+
name: "self"
80+
1: AST_NAME
81+
flags: NAME_NOT_FQ (%d)
82+
name: "Countable"
83+
attributes: null
84+
__declId: 0
85+
attributes: null
86+
__declId: 1
87+
1: AST_FUNC_DECL
88+
flags: 0
89+
name: "this_is_a_compile_error"
90+
docComment: null
91+
params: AST_PARAM_LIST
92+
stmts: AST_STMT_LIST
93+
returnType: AST_TYPE_INTERSECTION
94+
0: AST_TYPE
95+
flags: TYPE_ITERABLE (%d)
96+
1: AST_NAME
97+
flags: NAME_NOT_FQ (%d)
98+
name: "Countable"
99+
attributes: null
100+
__declId: 2

0 commit comments

Comments
 (0)