Skip to content

Commit d513a1e

Browse files
committed
Unify the parsing of all class-like member declarations
Using a class-like member in the wrong class-like is a compile-time error, not a parsing error. Tolerate it and parse the current/remaining statements and leave it to the applications to warn about using unexpected members in classes, traits, interfaces, or enums. (Or to tolerate them for stubs for editors/IDEs) Related to microsoft#395
1 parent 3ccba97 commit d513a1e

File tree

5 files changed

+110
-174
lines changed

5 files changed

+110
-174
lines changed

src/ParseContext.php

+2-5
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,13 @@
99
class ParseContext {
1010
const SourceElements = 0;
1111
const BlockStatements = 1;
12-
const ClassMembers = 2;
12+
const ClasslikeMembers = 2;
1313
const IfClause2Elements = 3;
1414
const SwitchStatementElements = 4;
1515
const CaseStatementElements = 5;
1616
const WhileStatementElements = 6;
1717
const ForStatementElements = 7;
1818
const ForeachStatementElements = 8;
1919
const DeclareStatementElements = 9;
20-
const InterfaceMembers = 10;
21-
const TraitMembers = 11;
22-
const Count = 12;
23-
const EnumMembers = 13;
20+
const Count = 10;
2421
}

src/Parser.php

+22-169
Original file line numberDiff line numberDiff line change
@@ -295,11 +295,8 @@ private function isListTerminator(int $parseContext) {
295295
case ParseContext::SourceElements:
296296
return false;
297297

298-
case ParseContext::InterfaceMembers:
299-
case ParseContext::ClassMembers:
298+
case ParseContext::ClasslikeMembers:
300299
case ParseContext::BlockStatements:
301-
case ParseContext::TraitMembers:
302-
case ParseContext::EnumMembers:
303300
return $tokenKind === TokenKind::CloseBraceToken;
304301
case ParseContext::SwitchStatementElements:
305302
return $tokenKind === TokenKind::CloseBraceToken || $tokenKind === TokenKind::EndSwitchKeyword;
@@ -345,18 +342,9 @@ private function isValidListElement($context, Token $token) {
345342
case ParseContext::DeclareStatementElements:
346343
return $this->isStatementStart($token);
347344

348-
case ParseContext::ClassMembers:
345+
case ParseContext::ClasslikeMembers:
349346
return $this->isClassMemberDeclarationStart($token);
350347

351-
case ParseContext::TraitMembers:
352-
return $this->isTraitMemberDeclarationStart($token);
353-
354-
case ParseContext::EnumMembers:
355-
return $this->isEnumMemberDeclarationStart($token);
356-
357-
case ParseContext::InterfaceMembers:
358-
return $this->isInterfaceMemberDeclarationStart($token);
359-
360348
case ParseContext::SwitchStatementElements:
361349
return
362350
$token->kind === TokenKind::CaseKeyword ||
@@ -376,17 +364,8 @@ private function getParseListElementFn($context) {
376364
case ParseContext::ForeachStatementElements:
377365
case ParseContext::DeclareStatementElements:
378366
return $this->parseStatementFn();
379-
case ParseContext::ClassMembers:
380-
return $this->parseClassElementFn();
381-
382-
case ParseContext::TraitMembers:
383-
return $this->parseTraitElementFn();
384-
385-
case ParseContext::InterfaceMembers:
386-
return $this->parseInterfaceElementFn();
387-
388-
case ParseContext::EnumMembers:
389-
return $this->parseEnumElementFn();
367+
case ParseContext::ClasslikeMembers:
368+
return $this->parseClasslikeElementFn();
390369

391370
case ParseContext::SwitchStatementElements:
392371
return $this->parseCaseOrDefaultStatement();
@@ -636,15 +615,19 @@ private function parseStatementFn() {
636615
};
637616
}
638617

639-
private function parseClassElementFn() {
618+
private function parseClasslikeElementFn() {
640619
return function ($parentNode) {
641620
$modifiers = $this->parseModifiers();
642621

643622
$token = $this->getCurrentToken();
623+
// Note that parsing the wrong type of class element in a classlike is a compile-time error, not a parse error.
644624
switch ($token->kind) {
645625
case TokenKind::ConstKeyword:
646626
return $this->parseClassConstDeclaration($parentNode, $modifiers);
647627

628+
case TokenKind::CaseKeyword:
629+
return $this->parseEnumCaseDeclaration($parentNode);
630+
648631
case TokenKind::FunctionKeyword:
649632
return $this->parseMethodDeclaration($parentNode, $modifiers);
650633

@@ -688,14 +671,14 @@ private function parseClassDeclaration($parentNode) : Node {
688671
$classNode->name->kind = TokenKind::Name;
689672
$classNode->classBaseClause = $this->parseClassBaseClause($classNode);
690673
$classNode->classInterfaceClause = $this->parseClassInterfaceClause($classNode);
691-
$classNode->classMembers = $this->parseClassMembers($classNode);
674+
$classNode->classMembers = $this->parseClasslikeMembers($classNode);
692675
return $classNode;
693676
}
694677

695-
private function parseClassMembers($parentNode) : ClassMembersNode {
678+
private function parseClasslikeMembers($parentNode) : ClassMembersNode {
696679
$classMembers = new ClassMembersNode();
697680
$classMembers->openBrace = $this->eat1(TokenKind::OpenBraceToken);
698-
$classMembers->classMemberDeclarations = $this->parseList($classMembers, ParseContext::ClassMembers);
681+
$classMembers->classMemberDeclarations = $this->parseList($classMembers, ParseContext::ClasslikeMembers);
699682
$classMembers->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
700683
$classMembers->parent = $parentNode;
701684
return $classMembers;
@@ -741,18 +724,9 @@ private function parseAttributeExpression($parentNode) {
741724
*/
742725
private function parseAttributeStatement($parentNode) {
743726
$attributeGroups = $this->parseAttributeGroups(null);
744-
if ($parentNode instanceof ClassMembersNode) {
727+
if ($parentNode instanceof ClassMembersNode || $parentNode instanceof TraitMembers || $parentNode instanceof EnumMembers || $parentNode instanceof InterfaceMembers) {
745728
// Create a class element or a MissingMemberDeclaration
746-
$statement = $this->parseClassElementFn()($parentNode);
747-
} elseif ($parentNode instanceof TraitMembers) {
748-
// Create a trait element or a MissingMemberDeclaration
749-
$statement = $this->parseTraitElementFn()($parentNode);
750-
} elseif ($parentNode instanceof EnumMembers) {
751-
// Create a enum element or a MissingMemberDeclaration
752-
$statement = $this->parseEnumElementFn()($parentNode);
753-
} elseif ($parentNode instanceof InterfaceMembers) {
754-
// Create an interface element or a MissingMemberDeclaration
755-
$statement = $this->parseInterfaceElementFn()($parentNode);
729+
$statement = $this->parseClasslikeElementFn()($parentNode);
756730
} else {
757731
// Classlikes, anonymous functions, global functions, and arrow functions can have attributes. Global constants cannot.
758732
if (in_array($this->token->kind, [TokenKind::ClassKeyword, TokenKind::TraitKeyword, TokenKind::InterfaceKeyword, TokenKind::AbstractKeyword, TokenKind::FinalKeyword, TokenKind::FunctionKeyword, TokenKind::FnKeyword, TokenKind::EnumKeyword], true) ||
@@ -1073,6 +1047,9 @@ private function isClassMemberDeclarationStart(Token $token) {
10731047

10741048
// attributes
10751049
case TokenKind::AttributeToken:
1050+
1051+
// enum
1052+
case TokenKind::CaseKeyword:
10761053
return true;
10771054

10781055
}
@@ -1417,7 +1394,7 @@ private function parseStringLiteralExpression2($parentNode): StringLiteral {
14171394
case TokenKind::DollarOpenBraceToken:
14181395
case TokenKind::OpenBraceDollarToken:
14191396
$expression->children[] = $this->eat(TokenKind::DollarOpenBraceToken, TokenKind::OpenBraceDollarToken);
1420-
/**
1397+
/**
14211398
* @phpstan-ignore-next-line "Strict comparison using
14221399
* === between 403|404 and 408 will always evaluate to
14231400
* false" is wrong because those tokens were eaten above
@@ -3286,7 +3263,7 @@ private function parseObjectCreationExpression($parentNode) {
32863263
$objectCreationExpression->classInterfaceClause = $this->parseClassInterfaceClause($objectCreationExpression);
32873264

32883265
if ($this->getCurrentToken()->kind === TokenKind::OpenBraceToken) {
3289-
$objectCreationExpression->classMembers = $this->parseClassMembers($objectCreationExpression);
3266+
$objectCreationExpression->classMembers = $this->parseClasslikeMembers($objectCreationExpression);
32903267
}
32913268

32923269
return $objectCreationExpression;
@@ -3474,7 +3451,7 @@ private function parseInterfaceDeclaration($parentNode): InterfaceDeclaration {
34743451
private function parseInterfaceMembers($parentNode) : InterfaceMembers {
34753452
$interfaceMembers = new InterfaceMembers();
34763453
$interfaceMembers->openBrace = $this->eat1(TokenKind::OpenBraceToken);
3477-
$interfaceMembers->interfaceMemberDeclarations = $this->parseList($interfaceMembers, ParseContext::InterfaceMembers);
3454+
$interfaceMembers->interfaceMemberDeclarations = $this->parseList($interfaceMembers, ParseContext::ClasslikeMembers);
34783455
$interfaceMembers->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
34793456
$interfaceMembers->parent = $parentNode;
34803457
return $interfaceMembers;
@@ -3507,30 +3484,6 @@ private function isInterfaceMemberDeclarationStart(Token $token) {
35073484
return false;
35083485
}
35093486

3510-
private function parseInterfaceElementFn() {
3511-
return function ($parentNode) {
3512-
$modifiers = $this->parseModifiers();
3513-
3514-
$token = $this->getCurrentToken();
3515-
switch ($token->kind) {
3516-
case TokenKind::ConstKeyword:
3517-
return $this->parseClassConstDeclaration($parentNode, $modifiers);
3518-
3519-
case TokenKind::FunctionKeyword:
3520-
return $this->parseMethodDeclaration($parentNode, $modifiers);
3521-
3522-
case TokenKind::AttributeToken:
3523-
return $this->parseAttributeStatement($parentNode);
3524-
3525-
default:
3526-
$missingInterfaceMemberDeclaration = new MissingMemberDeclaration();
3527-
$missingInterfaceMemberDeclaration->parent = $parentNode;
3528-
$missingInterfaceMemberDeclaration->modifiers = $modifiers;
3529-
return $missingInterfaceMemberDeclaration;
3530-
}
3531-
};
3532-
}
3533-
35343487
private function parseInterfaceBaseClause($parentNode) {
35353488
$interfaceBaseClause = new InterfaceBaseClause();
35363489
$interfaceBaseClause->parent = $parentNode;
@@ -3657,75 +3610,13 @@ private function parseTraitMembers($parentNode) {
36573610

36583611
$traitMembers->openBrace = $this->eat1(TokenKind::OpenBraceToken);
36593612

3660-
$traitMembers->traitMemberDeclarations = $this->parseList($traitMembers, ParseContext::TraitMembers);
3613+
$traitMembers->traitMemberDeclarations = $this->parseList($traitMembers, ParseContext::ClasslikeMembers);
36613614

36623615
$traitMembers->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
36633616

36643617
return $traitMembers;
36653618
}
36663619

3667-
private function isTraitMemberDeclarationStart($token) {
3668-
switch ($token->kind) {
3669-
// property-declaration
3670-
case TokenKind::VariableName:
3671-
3672-
// modifiers
3673-
case TokenKind::PublicKeyword:
3674-
case TokenKind::ProtectedKeyword:
3675-
case TokenKind::PrivateKeyword:
3676-
case TokenKind::VarKeyword:
3677-
case TokenKind::StaticKeyword:
3678-
case TokenKind::AbstractKeyword:
3679-
case TokenKind::FinalKeyword:
3680-
case TokenKind::ReadonlyKeyword:
3681-
case TokenKind::ConstKeyword:
3682-
3683-
// method-declaration
3684-
case TokenKind::FunctionKeyword:
3685-
3686-
// trait-use-clauses
3687-
case TokenKind::UseKeyword:
3688-
3689-
// attributes
3690-
case TokenKind::AttributeToken:
3691-
return true;
3692-
}
3693-
return false;
3694-
}
3695-
3696-
private function parseTraitElementFn() {
3697-
return function ($parentNode) {
3698-
$modifiers = $this->parseModifiers();
3699-
3700-
$token = $this->getCurrentToken();
3701-
switch ($token->kind) {
3702-
case TokenKind::ConstKeyword:
3703-
return $this->parseClassConstDeclaration($parentNode, $modifiers);
3704-
3705-
case TokenKind::FunctionKeyword:
3706-
return $this->parseMethodDeclaration($parentNode, $modifiers);
3707-
3708-
case TokenKind::QuestionToken:
3709-
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration(
3710-
$parentNode,
3711-
$modifiers,
3712-
$this->eat1(TokenKind::QuestionToken)
3713-
);
3714-
case TokenKind::VariableName:
3715-
return $this->parsePropertyDeclaration($parentNode, $modifiers);
3716-
3717-
case TokenKind::UseKeyword:
3718-
return $this->parseTraitUseClause($parentNode);
3719-
3720-
case TokenKind::AttributeToken:
3721-
return $this->parseAttributeStatement($parentNode);
3722-
3723-
default:
3724-
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration($parentNode, $modifiers);
3725-
}
3726-
};
3727-
}
3728-
37293620
private function parseEnumDeclaration($parentNode) {
37303621
$enumDeclaration = new EnumDeclaration();
37313622
$enumDeclaration->parent = $parentNode;
@@ -3765,7 +3656,7 @@ private function parseEnumMembers($parentNode) {
37653656

37663657
$enumMembers->openBrace = $this->eat1(TokenKind::OpenBraceToken);
37673658

3768-
$enumMembers->enumMemberDeclarations = $this->parseList($enumMembers, ParseContext::EnumMembers);
3659+
$enumMembers->enumMemberDeclarations = $this->parseList($enumMembers, ParseContext::ClasslikeMembers);
37693660

37703661
$enumMembers->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
37713662

@@ -3799,44 +3690,6 @@ private function isEnumMemberDeclarationStart($token) {
37993690
return false;
38003691
}
38013692

3802-
private function parseEnumElementFn() {
3803-
return function ($parentNode) {
3804-
$modifiers = $this->parseModifiers();
3805-
3806-
$token = $this->getCurrentToken();
3807-
switch ($token->kind) {
3808-
// TODO: CaseKeyword
3809-
case TokenKind::CaseKeyword:
3810-
return $this->parseEnumCaseDeclaration($parentNode);
3811-
3812-
case TokenKind::ConstKeyword:
3813-
return $this->parseClassConstDeclaration($parentNode, $modifiers);
3814-
3815-
case TokenKind::FunctionKeyword:
3816-
return $this->parseMethodDeclaration($parentNode, $modifiers);
3817-
3818-
case TokenKind::QuestionToken:
3819-
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration(
3820-
$parentNode,
3821-
$modifiers,
3822-
$this->eat1(TokenKind::QuestionToken)
3823-
);
3824-
case TokenKind::VariableName:
3825-
return $this->parsePropertyDeclaration($parentNode, $modifiers);
3826-
3827-
case TokenKind::UseKeyword:
3828-
return $this->parseTraitUseClause($parentNode);
3829-
3830-
case TokenKind::AttributeToken:
3831-
return $this->parseAttributeStatement($parentNode);
3832-
3833-
default:
3834-
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration($parentNode, $modifiers);
3835-
}
3836-
};
3837-
}
3838-
3839-
38403693
/**
38413694
* @param Node $parentNode
38423695
* @param Token[] $modifiers

tests/cases/parser81/enums8.php

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
class X {
3+
// Cases are only allowed in enums. But this is a compile-time error, not a parse error
4+
case F = 1;
5+
}

tests/cases/parser81/enums8.php.diag

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]

0 commit comments

Comments
 (0)