Skip to content

Commit 4c22c62

Browse files
TomasVotrubanikic
andauthored
[PHP 8.0] Add attributes support (nikic#661)
Adds support for PHP 8 attributes, represented using `AttrGroup` nodes containing `Attribute` nodes. The `attrGroup` subnode is added to all nodes that can have attributes. This is still missing FPPP support. Co-authored-by: Nikita Popov <[email protected]>
1 parent f66a32e commit 4c22c62

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+2518
-1199
lines changed

grammar/php7.y

Lines changed: 70 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,31 @@ no_comma:
8282
optional_comma:
8383
/* empty */
8484
| ','
85+
;
86+
87+
attribute_decl:
88+
class_name { $$ = Node\Attribute[$1, []]; }
89+
| class_name argument_list { $$ = Node\Attribute[$1, $2]; }
90+
;
91+
92+
attribute_group:
93+
attribute_decl { init($1); }
94+
| attribute_group ',' attribute_decl { push($1, $3); }
95+
;
96+
97+
attribute:
98+
T_ATTRIBUTE attribute_group optional_comma ']' { $$ = Node\AttributeGroup[$2]; }
99+
;
100+
101+
attributes:
102+
attribute { init($1); }
103+
| attributes attribute { push($1, $2); }
104+
;
105+
106+
optional_attributes:
107+
/* empty */ { $$ = []; }
108+
| attributes { $$ = $1; }
109+
;
85110

86111
top_statement:
87112
statement { $$ = $1; }
@@ -316,19 +341,24 @@ block_or_error:
316341
;
317342

318343
function_declaration_statement:
319-
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error
320-
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8]]; }
344+
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error
345+
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
346+
| attributes T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error
347+
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
321348
;
322349

323350
class_declaration_statement:
324351
class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
325-
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]];
352+
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6, 'attrGroups' => []]];
326353
$this->checkClass($$, #2); }
327-
| T_INTERFACE identifier interface_extends_list '{' class_statement_list '}'
328-
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]];
329-
$this->checkInterface($$, #2); }
330-
| T_TRAIT identifier '{' class_statement_list '}'
331-
{ $$ = Stmt\Trait_[$2, ['stmts' => $4]]; }
354+
| attributes class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
355+
{ $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
356+
$this->checkClass($$, #3); }
357+
| optional_attributes T_INTERFACE identifier interface_extends_list '{' class_statement_list '}'
358+
{ $$ = Stmt\Interface_[$3, ['extends' => $4, 'stmts' => $6, 'attrGroups' => $1]];
359+
$this->checkInterface($$, #3); }
360+
| optional_attributes T_TRAIT identifier '{' class_statement_list '}'
361+
{ $$ = Stmt\Trait_[$3, ['stmts' => $5, 'attrGroups' => $1]]; }
332362
;
333363

334364
class_entry_type:
@@ -489,14 +519,14 @@ optional_visibility_modifier:
489519
;
490520

491521
parameter:
492-
optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable
493-
{ $$ = new Node\Param($5, null, $2, $3, $4, attributes(), $1);
522+
optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable
523+
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1);
494524
$this->checkParam($$); }
495-
| optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable '=' expr
496-
{ $$ = new Node\Param($5, $7, $2, $3, $4, attributes(), $1);
525+
| optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable '=' expr
526+
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1);
497527
$this->checkParam($$); }
498-
| optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis error
499-
{ $$ = new Node\Param(Expr\Error[], null, $2, $3, $4, attributes(), $1); }
528+
| optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis error
529+
{ $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); }
500530
;
501531

502532
type_expr:
@@ -600,14 +630,15 @@ class_statement_list:
600630
;
601631

602632
class_statement:
603-
variable_modifiers optional_type_without_static property_declaration_list ';'
604-
{ $attrs = attributes();
605-
$$ = new Stmt\Property($1, $3, $attrs, $2); $this->checkProperty($$, #1); }
606-
| method_modifiers T_CONST class_const_list ';'
607-
{ $$ = Stmt\ClassConst[$3, $1]; $this->checkClassConst($$, #1); }
608-
| method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
609-
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]];
610-
$this->checkClassMethod($$, #1); }
633+
optional_attributes variable_modifiers optional_type_without_static property_declaration_list ';'
634+
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1);
635+
$this->checkProperty($$, #2); }
636+
| optional_attributes method_modifiers T_CONST class_const_list ';'
637+
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1);
638+
$this->checkClassConst($$, #2); }
639+
| optional_attributes method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
640+
{ $$ = Stmt\ClassMethod[$5, ['type' => $2, 'byRef' => $4, 'params' => $7, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]];
641+
$this->checkClassMethod($$, #2); }
611642
| T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
612643
| error { $$ = null; /* will be skipped */ }
613644
;
@@ -802,21 +833,27 @@ expr:
802833
| T_THROW expr { $$ = Expr\Throw_[$2]; }
803834

804835
| T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
805-
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $2, 'params' => $4, 'returnType' => $6, 'expr' => $8]]; }
836+
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $2, 'params' => $4, 'returnType' => $6, 'expr' => $8, 'attrGroups' => []]]; }
806837
| T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
807-
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9]]; }
808-
809-
| T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
810-
block_or_error
811-
{ $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8]]; }
812-
| T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
813-
block_or_error
814-
{ $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9]]; }
838+
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9, 'attrGroups' => []]]; }
839+
| T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
840+
{ $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
841+
| T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
842+
{ $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => []]]; }
843+
844+
| attributes T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
845+
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9, 'attrGroups' => $1]]; }
846+
| attributes T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
847+
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $4, 'params' => $6, 'returnType' => $8, 'expr' => $10, 'attrGroups' => $1]]; }
848+
| attributes T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
849+
{ $$ = Expr\Closure[['static' => false, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
850+
| attributes T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
851+
{ $$ = Expr\Closure[['static' => true, 'byRef' => $4, 'params' => $6, 'uses' => $8, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]]; }
815852
;
816853

817854
anonymous_class:
818-
T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
819-
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2);
855+
optional_attributes T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
856+
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]], $3);
820857
$this->checkClass($$[0], -1); }
821858
;
822859

grammar/rebuildParsers.php

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,28 +117,28 @@ function($matches) {
117117
$matches['args']
118118
);
119119

120-
if ('attributes' == $name) {
120+
if ('attributes' === $name) {
121121
assertArgs(0, $args, $name);
122122
return '$this->startAttributeStack[#1] + $this->endAttributes';
123123
}
124124

125-
if ('stackAttributes' == $name) {
125+
if ('stackAttributes' === $name) {
126126
assertArgs(1, $args, $name);
127127
return '$this->startAttributeStack[' . $args[0] . ']'
128128
. ' + $this->endAttributeStack[' . $args[0] . ']';
129129
}
130130

131-
if ('init' == $name) {
131+
if ('init' === $name) {
132132
return '$$ = array(' . implode(', ', $args) . ')';
133133
}
134134

135-
if ('push' == $name) {
135+
if ('push' === $name) {
136136
assertArgs(2, $args, $name);
137137

138138
return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0];
139139
}
140140

141-
if ('pushNormalizing' == $name) {
141+
if ('pushNormalizing' === $name) {
142142
assertArgs(2, $args, $name);
143143

144144
return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }'
@@ -151,20 +151,20 @@ function($matches) {
151151
return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
152152
}
153153

154-
if ('parseVar' == $name) {
154+
if ('parseVar' === $name) {
155155
assertArgs(1, $args, $name);
156156

157157
return 'substr(' . $args[0] . ', 1)';
158158
}
159159

160-
if ('parseEncapsed' == $name) {
160+
if ('parseEncapsed' === $name) {
161161
assertArgs(3, $args, $name);
162162

163163
return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
164164
. ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
165165
}
166166

167-
if ('makeNop' == $name) {
167+
if ('makeNop' === $name) {
168168
assertArgs(3, $args, $name);
169169

170170
return '$startAttributes = ' . $args[1] . ';'
@@ -182,15 +182,15 @@ function($matches) {
182182
. ' else { ' . $args[0] . ' = null; }';
183183
}
184184

185-
if ('strKind' == $name) {
185+
if ('strKind' === $name) {
186186
assertArgs(1, $args, $name);
187187

188188
return '(' . $args[0] . '[0] === "\'" || (' . $args[0] . '[1] === "\'" && '
189189
. '(' . $args[0] . '[0] === \'b\' || ' . $args[0] . '[0] === \'B\')) '
190190
. '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)';
191191
}
192192

193-
if ('prependLeadingComments' == $name) {
193+
if ('prependLeadingComments' === $name) {
194194
assertArgs(1, $args, $name);
195195

196196
return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; '

lib/PhpParser/Node/Attribute.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace PhpParser\Node;
4+
5+
use PhpParser\Node;
6+
use PhpParser\NodeAbstract;
7+
8+
class Attribute extends NodeAbstract
9+
{
10+
/** @var Name Attribute name */
11+
public $name;
12+
13+
/** @var Arg[] Attribute arguments */
14+
public $args;
15+
16+
/**
17+
* @param Node\Name $name Attribute name
18+
* @param Arg[] $args Attribute arguments
19+
* @param array $attributes Additional node attributes
20+
*/
21+
public function __construct(Name $name, array $args = [], array $attributes = []) {
22+
$this->attributes = $attributes;
23+
$this->name = $name;
24+
$this->args = $args;
25+
}
26+
27+
public function getSubNodeNames() : array {
28+
return ['name', 'args'];
29+
}
30+
31+
public function getType() : string {
32+
return 'Attribute';
33+
}
34+
}

lib/PhpParser/Node/AttributeGroup.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace PhpParser\Node;
4+
5+
use PhpParser\Node;
6+
use PhpParser\NodeAbstract;
7+
8+
class AttributeGroup extends NodeAbstract
9+
{
10+
/** @var Attribute[] Attributes */
11+
public $attrs;
12+
13+
/**
14+
* @param Attribute[] $attrs PHP attributes
15+
* @param array $attributes Additional node attributes
16+
*/
17+
public function __construct(array $attrs, array $attributes = []) {
18+
$this->attributes = $attributes;
19+
$this->attrs = $attrs;
20+
}
21+
22+
public function getSubNodeNames() : array {
23+
return ['attrs'];
24+
}
25+
26+
public function getType() : string {
27+
return 'AttributeGroup';
28+
}
29+
}

lib/PhpParser/Node/Expr/ArrowFunction.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class ArrowFunction extends Expr implements FunctionLike
2222

2323
/** @var Expr */
2424
public $expr;
25+
/** @var Node\AttributeGroup[] */
26+
public $attrGroups;
2527

2628
/**
2729
* @param array $subNodes Array of the following optional subnodes:
@@ -30,6 +32,7 @@ class ArrowFunction extends Expr implements FunctionLike
3032
* 'params' => array() : Parameters
3133
* 'returnType' => null : Return type
3234
* 'expr' => Expr : Expression body
35+
* 'attrGroups' => array() : PHP attribute groups
3336
* @param array $attributes Additional attributes
3437
*/
3538
public function __construct(array $subNodes = [], array $attributes = []) {
@@ -40,10 +43,11 @@ public function __construct(array $subNodes = [], array $attributes = []) {
4043
$returnType = $subNodes['returnType'] ?? null;
4144
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
4245
$this->expr = $subNodes['expr'] ?? null;
46+
$this->attrGroups = $subNodes['attrGroups'] ?? [];
4347
}
4448

4549
public function getSubNodeNames() : array {
46-
return ['static', 'byRef', 'params', 'returnType', 'expr'];
50+
return ['attrGroups', 'static', 'byRef', 'params', 'returnType', 'expr'];
4751
}
4852

4953
public function returnsByRef() : bool {
@@ -58,6 +62,10 @@ public function getReturnType() {
5862
return $this->returnType;
5963
}
6064

65+
public function getAttrGroups() : array {
66+
return $this->attrGroups;
67+
}
68+
6169
/**
6270
* @return Node\Stmt\Return_[]
6371
*/

lib/PhpParser/Node/Expr/Closure.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ class Closure extends Expr implements FunctionLike
2020
public $returnType;
2121
/** @var Node\Stmt[] Statements */
2222
public $stmts;
23+
/** @var Node\AttributeGroup[] PHP attribute groups */
24+
public $attrGroups;
2325

2426
/**
2527
* Constructs a lambda function node.
@@ -31,6 +33,7 @@ class Closure extends Expr implements FunctionLike
3133
* 'uses' => array(): use()s
3234
* 'returnType' => null : Return type
3335
* 'stmts' => array(): Statements
36+
* 'attrGroups' => array(): PHP attributes groups
3437
* @param array $attributes Additional attributes
3538
*/
3639
public function __construct(array $subNodes = [], array $attributes = []) {
@@ -42,10 +45,11 @@ public function __construct(array $subNodes = [], array $attributes = []) {
4245
$returnType = $subNodes['returnType'] ?? null;
4346
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
4447
$this->stmts = $subNodes['stmts'] ?? [];
48+
$this->attrGroups = $subNodes['attrGroups'] ?? [];
4549
}
4650

4751
public function getSubNodeNames() : array {
48-
return ['static', 'byRef', 'params', 'uses', 'returnType', 'stmts'];
52+
return ['attrGroups', 'static', 'byRef', 'params', 'uses', 'returnType', 'stmts'];
4953
}
5054

5155
public function returnsByRef() : bool {
@@ -64,7 +68,11 @@ public function getReturnType() {
6468
public function getStmts() : array {
6569
return $this->stmts;
6670
}
67-
71+
72+
public function getAttrGroups() : array {
73+
return $this->attrGroups;
74+
}
75+
6876
public function getType() : string {
6977
return 'Expr_Closure';
7078
}

0 commit comments

Comments
 (0)