[PHP 8.1] Add support for enums (#758)

RFC: https://wiki.php.net/rfc/enumerations

Co-authored-by: Nikita Popov <nikita.ppv@gmail.com>
This commit is contained in:
Tomas Votruba 2021-04-25 21:11:36 +02:00 committed by GitHub
parent f767b9fd9f
commit f68e1a43ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1835 additions and 1412 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*.y]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4

View File

@ -28,7 +28,7 @@ reserved_non_modifiers:
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT
| T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
| T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_FN
| T_MATCH
| T_MATCH | T_ENUM
;
semi_reserved:
@ -356,6 +356,18 @@ class_declaration_statement:
$this->checkInterface($$, #3); }
| optional_attributes T_TRAIT identifier '{' class_statement_list '}'
{ $$ = Stmt\Trait_[$3, ['stmts' => $5, 'attrGroups' => $1]]; }
| optional_attributes T_ENUM identifier enum_scalar_type implements_list '{' class_statement_list '}'
{ $$ = Stmt\Enum_[$3, ['scalarType' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
$this->checkEnum($$, #3); }
;
enum_scalar_type:
/* empty */ { $$ = null; }
| ':' type { $$ = $2; }
enum_case_expr:
/* empty */ { $$ = null; }
| '=' expr { $$ = $2; }
;
class_entry_type:
@ -637,6 +649,8 @@ class_statement:
{ $$ = Stmt\ClassMethod[$5, ['type' => $2, 'byRef' => $4, 'params' => $7, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]];
$this->checkClassMethod($$, #2); }
| T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
| optional_attributes T_CASE identifier enum_case_expr semi
{ $$ = Stmt\EnumCase[$3, $4, $1]; }
| error { $$ = null; /* will be skipped */ }
;

View File

@ -83,6 +83,7 @@
%token T_CLASS
%token T_TRAIT
%token T_INTERFACE
%token T_ENUM
%token T_EXTENDS
%token T_IMPLEMENTS
%token T_OBJECT_OPERATOR

View File

@ -32,7 +32,7 @@ class Class_ extends ClassLike
* 'extends' => null : Name of extended class
* 'implements' => array(): Names of implemented interfaces
* 'stmts' => array(): Statements
* '$attrGroups' => array(): PHP attribute groups
* 'attrGroups' => array(): PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct($name, array $subNodes = [], array $attributes = []) {

View File

@ -0,0 +1,37 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Node\AttributeGroup;
class EnumCase extends Node\Stmt
{
/** @var Node\Identifier Enum case name */
public $name;
/** @var Node\Expr|null Enum case expression */
public $expr;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/**
* @param string|Node\Identifier $name Enum case name
* @param Node\Expr|null $expr Enum case expression
* @param AttributeGroup[] $attrGroups PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct($name, Node\Expr $expr = null, array $attrGroups = [], array $attributes = []) {
parent::__construct($attributes);
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
$this->expr = $expr;
$this->attrGroups = $attrGroups;
}
public function getSubNodeNames() : array {
return ['attrGroups', 'name', 'expr'];
}
public function getType() : string {
return 'Stmt_EnumCase';
}
}

View File

@ -0,0 +1,40 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Enum_ extends ClassLike
{
/** @var null|Node\Identifier Scalar Type */
public $scalarType;
/** @var Node\Name[] Names of implemented interfaces */
public $implements;
/**
* @param string|Node\Identifier|null $name Name
* @param array $subNodes Array of the following optional subnodes:
* 'scalarType' => null : Scalar type
* 'implements' => array() : Names of implemented interfaces
* 'stmts' => array() : Statements
* 'attrGroups' => array() : PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct($name, array $subNodes = [], array $attributes = []) {
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
$this->scalarType = $subNodes['scalarType'] ?? null;
$this->implements = $subNodes['implements'] ?? [];
$this->stmts = $subNodes['stmts'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
parent::__construct($attributes);
}
public function getSubNodeNames() : array {
return ['attrGroups', 'name', 'scalarType', 'implements', 'stmts'];
}
public function getType() : string {
return 'Stmt_Enum';
}
}

View File

@ -193,9 +193,9 @@ class Php5 extends \PhpParser\ParserAbstract
"'`'",
"']'",
"'\"'",
"T_ENUM",
"T_NULLSAFE_OBJECT_OPERATOR",
"T_ATTRIBUTE",
"T_ENUM"
"T_ATTRIBUTE"
);
protected $tokenToSymbol = array(
@ -235,10 +235,10 @@ class Php5 extends \PhpParser\ParserAbstract
94, 95, 96, 97, 98, 99, 100, 101, 102, 103,
104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
114, 115, 116, 117, 118, 119, 120, 121, 122, 123,
124, 125, 126, 127, 128, 129, 130, 131, 163, 132,
133, 134, 135, 136, 137, 138, 139, 140, 141, 142,
143, 144, 145, 146, 147, 148, 149, 150, 151, 152,
153, 164, 165
124, 125, 126, 127, 128, 163, 129, 130, 131, 164,
132, 133, 134, 135, 136, 137, 138, 139, 140, 141,
142, 143, 144, 145, 146, 147, 148, 149, 150, 151,
152, 153, 165
);
protected $action = array(

File diff suppressed because it is too large Load Diff

View File

@ -114,32 +114,32 @@ final class Tokens
const T_CLASS = 362;
const T_TRAIT = 363;
const T_INTERFACE = 364;
const T_EXTENDS = 365;
const T_IMPLEMENTS = 366;
const T_OBJECT_OPERATOR = 367;
const T_NULLSAFE_OBJECT_OPERATOR = 368;
const T_LIST = 369;
const T_ARRAY = 370;
const T_CALLABLE = 371;
const T_CLASS_C = 372;
const T_TRAIT_C = 373;
const T_METHOD_C = 374;
const T_FUNC_C = 375;
const T_LINE = 376;
const T_FILE = 377;
const T_START_HEREDOC = 378;
const T_END_HEREDOC = 379;
const T_DOLLAR_OPEN_CURLY_BRACES = 380;
const T_CURLY_OPEN = 381;
const T_PAAMAYIM_NEKUDOTAYIM = 382;
const T_NAMESPACE = 383;
const T_NS_C = 384;
const T_DIR = 385;
const T_NS_SEPARATOR = 386;
const T_ELLIPSIS = 387;
const T_NAME_FULLY_QUALIFIED = 388;
const T_NAME_QUALIFIED = 389;
const T_NAME_RELATIVE = 390;
const T_ATTRIBUTE = 391;
const T_ENUM = 392;
const T_ENUM = 365;
const T_EXTENDS = 366;
const T_IMPLEMENTS = 367;
const T_OBJECT_OPERATOR = 368;
const T_NULLSAFE_OBJECT_OPERATOR = 369;
const T_LIST = 370;
const T_ARRAY = 371;
const T_CALLABLE = 372;
const T_CLASS_C = 373;
const T_TRAIT_C = 374;
const T_METHOD_C = 375;
const T_FUNC_C = 376;
const T_LINE = 377;
const T_FILE = 378;
const T_START_HEREDOC = 379;
const T_END_HEREDOC = 380;
const T_DOLLAR_OPEN_CURLY_BRACES = 381;
const T_CURLY_OPEN = 382;
const T_PAAMAYIM_NEKUDOTAYIM = 383;
const T_NAMESPACE = 384;
const T_NS_C = 385;
const T_DIR = 386;
const T_NS_SEPARATOR = 387;
const T_ELLIPSIS = 388;
const T_NAME_FULLY_QUALIFIED = 389;
const T_NAME_QUALIFIED = 390;
const T_NAME_RELATIVE = 391;
const T_ATTRIBUTE = 392;
}

View File

@ -16,6 +16,7 @@ use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Enum_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Property;
@ -912,22 +913,17 @@ abstract class ParserAbstract implements Parser
}
}
protected function checkClass(Class_ $node, $namePos) {
if (null !== $node->name && $node->name->isSpecialClassName()) {
private function checkClassName($name, $namePos) {
if (null !== $name && $name->isSpecialClassName()) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
sprintf('Cannot use \'%s\' as class name as it is reserved', $name),
$this->getAttributesAt($namePos)
));
}
}
if ($node->extends && $node->extends->isSpecialClassName()) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends),
$node->extends->getAttributes()
));
}
foreach ($node->implements as $interface) {
private function checkImplementedInterfaces(array $interfaces) {
foreach ($interfaces as $interface) {
if ($interface->isSpecialClassName()) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
@ -937,22 +933,27 @@ abstract class ParserAbstract implements Parser
}
}
protected function checkInterface(Interface_ $node, $namePos) {
if (null !== $node->name && $node->name->isSpecialClassName()) {
protected function checkClass(Class_ $node, $namePos) {
$this->checkClassName($node->name, $namePos);
if ($node->extends && $node->extends->isSpecialClassName()) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
$this->getAttributesAt($namePos)
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends),
$node->extends->getAttributes()
));
}
foreach ($node->extends as $interface) {
if ($interface->isSpecialClassName()) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
$interface->getAttributes()
));
}
}
$this->checkImplementedInterfaces($node->implements);
}
protected function checkInterface(Interface_ $node, $namePos) {
$this->checkClassName($node->name, $namePos);
$this->checkImplementedInterfaces($node->extends);
}
protected function checkEnum(Enum_ $node, $namePos) {
$this->checkClassName($node->name, $namePos);
$this->checkImplementedInterfaces($node->implements);
}
protected function checkClassMethod(ClassMethod $node, $modifierPos) {

View File

@ -727,6 +727,13 @@ class Standard extends PrettyPrinterAbstract
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_Enum(Stmt\Enum_ $node) {
return $this->pAttrGroups($node->attrGroups)
. 'enum ' . $node->name
. (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '')
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_Class(Stmt\Class_ $node) {
return $this->pClassCommon($node, ' ' . $node->name);
}
@ -737,6 +744,13 @@ class Standard extends PrettyPrinterAbstract
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_EnumCase(Stmt\EnumCase $node) {
return $this->pAttrGroups($node->attrGroups)
. 'case ' . $node->name
. ($node->expr ? ' = ' . $this->p($node->expr) : '')
. ';';
}
protected function pStmt_TraitUse(Stmt\TraitUse $node) {
return 'use ' . $this->pCommaSeparated($node->traits)
. (empty($node->adaptations)

View File

@ -1242,7 +1242,7 @@ abstract class PrettyPrinterAbstract
/**
* Lazily initializes the removal map.
*
* The removal map is used to determine which additional tokens should be returned when a
* The removal map is used to determine which additional tokens should be removed when a
* certain node is replaced by null.
*/
protected function initializeRemovalMap() {
@ -1269,6 +1269,8 @@ abstract class PrettyPrinterAbstract
'Stmt_Catch->var' => $stripLeft,
'Stmt_ClassMethod->returnType' => $stripColon,
'Stmt_Class->extends' => ['left' => \T_EXTENDS],
'Stmt_Enum->scalarType' => $stripColon,
'Stmt_EnumCase->expr' => $stripEquals,
'Expr_PrintableNewAnonClass->extends' => ['left' => \T_EXTENDS],
'Stmt_Continue->num' => $stripBoth,
'Stmt_Foreach->keyVar' => $stripDoubleArrow,
@ -1307,6 +1309,8 @@ abstract class PrettyPrinterAbstract
'Stmt_Catch->var' => [null, false, ' ', null],
'Stmt_ClassMethod->returnType' => [')', false, ' : ', null],
'Stmt_Class->extends' => [null, false, ' extends ', null],
'Stmt_Enum->scalarType' => [null, false, ' : ', null],
'Stmt_EnumCase->expr' => [null, false, ' = ', null],
'Expr_PrintableNewAnonClass->extends' => [null, ' extends ', null],
'Stmt_Continue->num' => [\T_CONTINUE, false, ' ', null],
'Stmt_Foreach->keyVar' => [\T_AS, false, null, ' => '],
@ -1356,6 +1360,7 @@ abstract class PrettyPrinterAbstract
'Stmt_ClassConst->consts' => ', ',
'Stmt_ClassMethod->params' => ', ',
'Stmt_Class->implements' => ', ',
'Stmt_Enum->implements' => ', ',
'Expr_PrintableNewAnonClass->implements' => ', ',
'Stmt_Const->consts' => ', ',
'Stmt_Declare->declares' => ', ',
@ -1382,6 +1387,7 @@ abstract class PrettyPrinterAbstract
'Stmt_Case->stmts' => "\n",
'Stmt_Catch->stmts' => "\n",
'Stmt_Class->stmts' => "\n",
'Stmt_Enum->stmts' => "\n",
'Expr_PrintableNewAnonClass->stmts' => "\n",
'Stmt_Interface->stmts' => "\n",
'Stmt_Trait->stmts' => "\n",
@ -1397,6 +1403,8 @@ abstract class PrettyPrinterAbstract
'Stmt_If->stmts' => "\n",
'Stmt_Namespace->stmts' => "\n",
'Stmt_Class->attrGroups' => "\n",
'Stmt_Enum->attrGroups' => "\n",
'Stmt_EnumCase->attrGroups' => "\n",
'Stmt_Interface->attrGroups' => "\n",
'Stmt_Trait->attrGroups' => "\n",
'Stmt_Function->attrGroups' => "\n",
@ -1435,6 +1443,7 @@ abstract class PrettyPrinterAbstract
'Expr_PrintableNewAnonClass->implements' => [null, ' implements ', ''],
'Expr_StaticCall->args' => ['(', '', ''],
'Stmt_Class->implements' => [null, ' implements ', ''],
'Stmt_Enum->implements' => [null, ' implements ', ''],
'Stmt_ClassMethod->params' => ['(', '', ''],
'Stmt_Interface->extends' => [null, ' extends ', ''],
'Stmt_Function->params' => ['(', '', ''],

View File

@ -0,0 +1,100 @@
Enum formatting preservation
-----
<?php
enum X: int
{}
-----
$stmts[0]->scalarType = null;
-----
<?php
enum X
{}
-----
<?php
enum X {
case
Y = 1;
}
-----
$stmts[0]->stmts[0]->expr = null;
-----
<?php
enum X {
case
Y;
}
-----
<?php
enum X
{}
-----
$stmts[0]->scalarType = new Node\Identifier('int');
-----
<?php
enum X : int
{}
-----
<?php
enum X
implements Y
{}
-----
$stmts[0]->scalarType = new Node\Identifier('int');
-----
<?php
enum X : int
implements Y
{}
-----
<?php
enum X {
case
Y;
}
-----
$stmts[0]->stmts[0]->expr = new Scalar\LNumber(1);
-----
<?php
enum X {
case
Y = 1;
}
-----
<?php
enum X {
case A;
case B;
}
-----
$stmts[0]->stmts[] = new Node\Stmt\EnumCase('C');
-----
<?php
enum X {
case A;
case B;
case C;
}
-----
<?php
enum X
implements Y
{}
-----
$stmts[0]->implements[] = new Node\Name('Z');
-----
<?php
enum X
implements Y, Z
{}
-----
<?php
enum X
{}
-----
$stmts[0]->implements[] = new Node\Name('Y');
-----
<?php
enum X implements Y
{}

View File

@ -0,0 +1,65 @@
Enum
-----
<?php
enum A {}
enum B implements Bar, Baz {
}
enum C: int implements Bar {}
-----
!!php7
array(
0: Stmt_Enum(
attrGroups: array(
)
name: Identifier(
name: A
)
scalarType: null
implements: array(
)
stmts: array(
)
)
1: Stmt_Enum(
attrGroups: array(
)
name: Identifier(
name: B
)
scalarType: null
implements: array(
0: Name(
parts: array(
0: Bar
)
)
1: Name(
parts: array(
0: Baz
)
)
)
stmts: array(
)
)
2: Stmt_Enum(
attrGroups: array(
)
name: Identifier(
name: C
)
scalarType: Identifier(
name: int
)
implements: array(
0: Name(
parts: array(
0: Bar
)
)
)
stmts: array(
)
)
)

View File

@ -0,0 +1,67 @@
Enum
-----
<?php
enum Suit: string
{
case Hearts = 'H';
case Diamonds;
case Clubs = 'C';
case Spades = 'S';
}
-----
!!php7
array(
0: Stmt_Enum(
attrGroups: array(
)
name: Identifier(
name: Suit
)
scalarType: Identifier(
name: string
)
implements: array(
)
stmts: array(
0: Stmt_EnumCase(
attrGroups: array(
)
name: Identifier(
name: Hearts
)
expr: Scalar_String(
value: H
)
)
1: Stmt_EnumCase(
attrGroups: array(
)
name: Identifier(
name: Diamonds
)
expr: null
)
2: Stmt_EnumCase(
attrGroups: array(
)
name: Identifier(
name: Clubs
)
expr: Scalar_String(
value: C
)
)
3: Stmt_EnumCase(
attrGroups: array(
)
name: Identifier(
name: Spades
)
expr: Scalar_String(
value: S
)
)
)
)
)

View File

@ -0,0 +1,39 @@
Enum
-----
<?php
enum A implements B
{
case X;
case Y;
public function foo() {}
}
enum B: int {
case X = 1;
case Y = 2;
}
enum C: string implements D {
case Z = 'A';
}
-----
!!php7
enum A implements B
{
case X;
case Y;
public function foo()
{
}
}
enum B
{
case X = 1;
case Y = 2;
}
enum C implements D
{
case Z = 'A';
}