Skip to content

Commit 049aa8d

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 #395
1 parent 3ccba97 commit 049aa8d

File tree

5 files changed

+112
-230
lines changed

5 files changed

+112
-230
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

+24-225
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,17 +342,8 @@ private function isValidListElement($context, Token $token) {
345342
case ParseContext::DeclareStatementElements:
346343
return $this->isStatementStart($token);
347344

348-
case ParseContext::ClassMembers:
349-
return $this->isClassMemberDeclarationStart($token);
350-
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);
345+
case ParseContext::ClasslikeMembers:
346+
return $this->isClasslikeMemberDeclarationStart($token);
359347

360348
case ParseContext::SwitchStatementElements:
361349
return
@@ -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) ||
@@ -1045,7 +1019,7 @@ private function parseCompoundStatement($parentNode) {
10451019
return $compoundStatement;
10461020
}
10471021

1048-
private function isClassMemberDeclarationStart(Token $token) {
1022+
private function isClasslikeMemberDeclarationStart(Token $token) {
10491023
switch ($token->kind) {
10501024
// const-modifier
10511025
case TokenKind::ConstKeyword:
@@ -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,63 +3451,12 @@ 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;
34813458
}
34823459

3483-
private function isInterfaceMemberDeclarationStart(Token $token) {
3484-
switch ($token->kind) {
3485-
// visibility-modifier
3486-
case TokenKind::PublicKeyword:
3487-
case TokenKind::ProtectedKeyword:
3488-
case TokenKind::PrivateKeyword:
3489-
3490-
// static-modifier
3491-
case TokenKind::StaticKeyword:
3492-
3493-
// readonly-modifier
3494-
case TokenKind::ReadonlyKeyword:
3495-
3496-
// class-modifier
3497-
case TokenKind::AbstractKeyword:
3498-
case TokenKind::FinalKeyword:
3499-
3500-
case TokenKind::ConstKeyword:
3501-
3502-
case TokenKind::FunctionKeyword:
3503-
3504-
case TokenKind::AttributeToken:
3505-
return true;
3506-
}
3507-
return false;
3508-
}
3509-
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-
35343460
private function parseInterfaceBaseClause($parentNode) {
35353461
$interfaceBaseClause = new InterfaceBaseClause();
35363462
$interfaceBaseClause->parent = $parentNode;
@@ -3657,75 +3583,13 @@ private function parseTraitMembers($parentNode) {
36573583

36583584
$traitMembers->openBrace = $this->eat1(TokenKind::OpenBraceToken);
36593585

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

36623588
$traitMembers->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
36633589

36643590
return $traitMembers;
36653591
}
36663592

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-
37293593
private function parseEnumDeclaration($parentNode) {
37303594
$enumDeclaration = new EnumDeclaration();
37313595
$enumDeclaration->parent = $parentNode;
@@ -3765,78 +3629,13 @@ private function parseEnumMembers($parentNode) {
37653629

37663630
$enumMembers->openBrace = $this->eat1(TokenKind::OpenBraceToken);
37673631

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

37703634
$enumMembers->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
37713635

37723636
return $enumMembers;
37733637
}
37743638

3775-
private function isEnumMemberDeclarationStart($token) {
3776-
switch ($token->kind) {
3777-
// modifiers
3778-
case TokenKind::PublicKeyword:
3779-
case TokenKind::ProtectedKeyword:
3780-
case TokenKind::PrivateKeyword:
3781-
case TokenKind::StaticKeyword:
3782-
case TokenKind::AbstractKeyword:
3783-
case TokenKind::FinalKeyword:
3784-
3785-
// method-declaration
3786-
case TokenKind::FunctionKeyword:
3787-
3788-
// trait-use-clauses (enums can use traits)
3789-
case TokenKind::UseKeyword:
3790-
3791-
// cases and constants
3792-
case TokenKind::CaseKeyword:
3793-
case TokenKind::ConstKeyword:
3794-
3795-
// attributes
3796-
case TokenKind::AttributeToken:
3797-
return true;
3798-
}
3799-
return false;
3800-
}
3801-
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-
38403639
/**
38413640
* @param Node $parentNode
38423641
* @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)