Skip to content

Unify the parsing of all class-like member declarations #408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/ParseContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,13 @@
class ParseContext {
const SourceElements = 0;
const BlockStatements = 1;
const ClassMembers = 2;
const ClasslikeMembers = 2;
const IfClause2Elements = 3;
const SwitchStatementElements = 4;
const CaseStatementElements = 5;
const WhileStatementElements = 6;
const ForStatementElements = 7;
const ForeachStatementElements = 8;
const DeclareStatementElements = 9;
const InterfaceMembers = 10;
const TraitMembers = 11;
const Count = 12;
const EnumMembers = 13;
const Count = 10;
}
243 changes: 21 additions & 222 deletions src/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,8 @@ private function isListTerminator(int $parseContext) {
case ParseContext::SourceElements:
return false;

case ParseContext::InterfaceMembers:
case ParseContext::ClassMembers:
case ParseContext::ClasslikeMembers:
case ParseContext::BlockStatements:
case ParseContext::TraitMembers:
case ParseContext::EnumMembers:
return $tokenKind === TokenKind::CloseBraceToken;
case ParseContext::SwitchStatementElements:
return $tokenKind === TokenKind::CloseBraceToken || $tokenKind === TokenKind::EndSwitchKeyword;
Expand Down Expand Up @@ -345,17 +342,8 @@ private function isValidListElement($context, Token $token) {
case ParseContext::DeclareStatementElements:
return $this->isStatementStart($token);

case ParseContext::ClassMembers:
return $this->isClassMemberDeclarationStart($token);

case ParseContext::TraitMembers:
return $this->isTraitMemberDeclarationStart($token);

case ParseContext::EnumMembers:
return $this->isEnumMemberDeclarationStart($token);

case ParseContext::InterfaceMembers:
return $this->isInterfaceMemberDeclarationStart($token);
case ParseContext::ClasslikeMembers:
return $this->isClasslikeMemberDeclarationStart($token);

case ParseContext::SwitchStatementElements:
return
Expand All @@ -376,17 +364,8 @@ private function getParseListElementFn($context) {
case ParseContext::ForeachStatementElements:
case ParseContext::DeclareStatementElements:
return $this->parseStatementFn();
case ParseContext::ClassMembers:
return $this->parseClassElementFn();

case ParseContext::TraitMembers:
return $this->parseTraitElementFn();

case ParseContext::InterfaceMembers:
return $this->parseInterfaceElementFn();

case ParseContext::EnumMembers:
return $this->parseEnumElementFn();
case ParseContext::ClasslikeMembers:
return $this->parseClasslikeElementFn();

case ParseContext::SwitchStatementElements:
return $this->parseCaseOrDefaultStatement();
Expand Down Expand Up @@ -636,15 +615,19 @@ private function parseStatementFn() {
};
}

private function parseClassElementFn() {
private function parseClasslikeElementFn() {
return function ($parentNode) {
$modifiers = $this->parseModifiers();

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

case TokenKind::CaseKeyword:
return $this->parseEnumCaseDeclaration($parentNode);

case TokenKind::FunctionKeyword:
return $this->parseMethodDeclaration($parentNode, $modifiers);

Expand Down Expand Up @@ -695,7 +678,7 @@ private function parseClassDeclaration($parentNode) : Node {
private function parseClassMembers($parentNode) : ClassMembersNode {
$classMembers = new ClassMembersNode();
$classMembers->openBrace = $this->eat1(TokenKind::OpenBraceToken);
$classMembers->classMemberDeclarations = $this->parseList($classMembers, ParseContext::ClassMembers);
$classMembers->classMemberDeclarations = $this->parseList($classMembers, ParseContext::ClasslikeMembers);
$classMembers->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
$classMembers->parent = $parentNode;
return $classMembers;
Expand Down Expand Up @@ -741,18 +724,9 @@ private function parseAttributeExpression($parentNode) {
*/
private function parseAttributeStatement($parentNode) {
$attributeGroups = $this->parseAttributeGroups(null);
if ($parentNode instanceof ClassMembersNode) {
if ($parentNode instanceof ClassMembersNode || $parentNode instanceof TraitMembers || $parentNode instanceof EnumMembers || $parentNode instanceof InterfaceMembers) {
// Create a class element or a MissingMemberDeclaration
$statement = $this->parseClassElementFn()($parentNode);
} elseif ($parentNode instanceof TraitMembers) {
// Create a trait element or a MissingMemberDeclaration
$statement = $this->parseTraitElementFn()($parentNode);
} elseif ($parentNode instanceof EnumMembers) {
// Create a enum element or a MissingMemberDeclaration
$statement = $this->parseEnumElementFn()($parentNode);
} elseif ($parentNode instanceof InterfaceMembers) {
// Create an interface element or a MissingMemberDeclaration
$statement = $this->parseInterfaceElementFn()($parentNode);
$statement = $this->parseClasslikeElementFn()($parentNode);
} else {
// Classlikes, anonymous functions, global functions, and arrow functions can have attributes. Global constants cannot.
if (in_array($this->token->kind, [TokenKind::ClassKeyword, TokenKind::TraitKeyword, TokenKind::InterfaceKeyword, TokenKind::AbstractKeyword, TokenKind::FinalKeyword, TokenKind::FunctionKeyword, TokenKind::FnKeyword, TokenKind::EnumKeyword], true) ||
Expand Down Expand Up @@ -1045,7 +1019,7 @@ private function parseCompoundStatement($parentNode) {
return $compoundStatement;
}

private function isClassMemberDeclarationStart(Token $token) {
private function isClasslikeMemberDeclarationStart(Token $token) {
switch ($token->kind) {
// const-modifier
case TokenKind::ConstKeyword:
Expand Down Expand Up @@ -1073,6 +1047,9 @@ private function isClassMemberDeclarationStart(Token $token) {

// attributes
case TokenKind::AttributeToken:

// enum
case TokenKind::CaseKeyword:
return true;

}
Expand Down Expand Up @@ -1417,7 +1394,7 @@ private function parseStringLiteralExpression2($parentNode): StringLiteral {
case TokenKind::DollarOpenBraceToken:
case TokenKind::OpenBraceDollarToken:
$expression->children[] = $this->eat(TokenKind::DollarOpenBraceToken, TokenKind::OpenBraceDollarToken);
/**
/**
* @phpstan-ignore-next-line "Strict comparison using
* === between 403|404 and 408 will always evaluate to
* false" is wrong because those tokens were eaten above
Expand Down Expand Up @@ -3474,63 +3451,12 @@ private function parseInterfaceDeclaration($parentNode): InterfaceDeclaration {
private function parseInterfaceMembers($parentNode) : InterfaceMembers {
$interfaceMembers = new InterfaceMembers();
$interfaceMembers->openBrace = $this->eat1(TokenKind::OpenBraceToken);
$interfaceMembers->interfaceMemberDeclarations = $this->parseList($interfaceMembers, ParseContext::InterfaceMembers);
$interfaceMembers->interfaceMemberDeclarations = $this->parseList($interfaceMembers, ParseContext::ClasslikeMembers);
$interfaceMembers->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
$interfaceMembers->parent = $parentNode;
return $interfaceMembers;
}

private function isInterfaceMemberDeclarationStart(Token $token) {
switch ($token->kind) {
// visibility-modifier
case TokenKind::PublicKeyword:
case TokenKind::ProtectedKeyword:
case TokenKind::PrivateKeyword:

// static-modifier
case TokenKind::StaticKeyword:

// readonly-modifier
case TokenKind::ReadonlyKeyword:

// class-modifier
case TokenKind::AbstractKeyword:
case TokenKind::FinalKeyword:

case TokenKind::ConstKeyword:

case TokenKind::FunctionKeyword:

case TokenKind::AttributeToken:
return true;
}
return false;
}

private function parseInterfaceElementFn() {
return function ($parentNode) {
$modifiers = $this->parseModifiers();

$token = $this->getCurrentToken();
switch ($token->kind) {
case TokenKind::ConstKeyword:
return $this->parseClassConstDeclaration($parentNode, $modifiers);

case TokenKind::FunctionKeyword:
return $this->parseMethodDeclaration($parentNode, $modifiers);

case TokenKind::AttributeToken:
return $this->parseAttributeStatement($parentNode);

default:
$missingInterfaceMemberDeclaration = new MissingMemberDeclaration();
$missingInterfaceMemberDeclaration->parent = $parentNode;
$missingInterfaceMemberDeclaration->modifiers = $modifiers;
return $missingInterfaceMemberDeclaration;
}
};
}

private function parseInterfaceBaseClause($parentNode) {
$interfaceBaseClause = new InterfaceBaseClause();
$interfaceBaseClause->parent = $parentNode;
Expand Down Expand Up @@ -3657,75 +3583,13 @@ private function parseTraitMembers($parentNode) {

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

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

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

return $traitMembers;
}

private function isTraitMemberDeclarationStart($token) {
switch ($token->kind) {
// property-declaration
case TokenKind::VariableName:

// modifiers
case TokenKind::PublicKeyword:
case TokenKind::ProtectedKeyword:
case TokenKind::PrivateKeyword:
case TokenKind::VarKeyword:
case TokenKind::StaticKeyword:
case TokenKind::AbstractKeyword:
case TokenKind::FinalKeyword:
case TokenKind::ReadonlyKeyword:
case TokenKind::ConstKeyword:

// method-declaration
case TokenKind::FunctionKeyword:

// trait-use-clauses
case TokenKind::UseKeyword:

// attributes
case TokenKind::AttributeToken:
return true;
}
return false;
}

private function parseTraitElementFn() {
return function ($parentNode) {
$modifiers = $this->parseModifiers();

$token = $this->getCurrentToken();
switch ($token->kind) {
case TokenKind::ConstKeyword:
return $this->parseClassConstDeclaration($parentNode, $modifiers);

case TokenKind::FunctionKeyword:
return $this->parseMethodDeclaration($parentNode, $modifiers);

case TokenKind::QuestionToken:
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration(
$parentNode,
$modifiers,
$this->eat1(TokenKind::QuestionToken)
);
case TokenKind::VariableName:
return $this->parsePropertyDeclaration($parentNode, $modifiers);

case TokenKind::UseKeyword:
return $this->parseTraitUseClause($parentNode);

case TokenKind::AttributeToken:
return $this->parseAttributeStatement($parentNode);

default:
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration($parentNode, $modifiers);
}
};
}

private function parseEnumDeclaration($parentNode) {
$enumDeclaration = new EnumDeclaration();
$enumDeclaration->parent = $parentNode;
Expand Down Expand Up @@ -3765,78 +3629,13 @@ private function parseEnumMembers($parentNode) {

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

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

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

return $enumMembers;
}

private function isEnumMemberDeclarationStart($token) {
switch ($token->kind) {
// modifiers
case TokenKind::PublicKeyword:
case TokenKind::ProtectedKeyword:
case TokenKind::PrivateKeyword:
case TokenKind::StaticKeyword:
case TokenKind::AbstractKeyword:
case TokenKind::FinalKeyword:

// method-declaration
case TokenKind::FunctionKeyword:

// trait-use-clauses (enums can use traits)
case TokenKind::UseKeyword:

// cases and constants
case TokenKind::CaseKeyword:
case TokenKind::ConstKeyword:

// attributes
case TokenKind::AttributeToken:
return true;
}
return false;
}

private function parseEnumElementFn() {
return function ($parentNode) {
$modifiers = $this->parseModifiers();

$token = $this->getCurrentToken();
switch ($token->kind) {
// TODO: CaseKeyword
case TokenKind::CaseKeyword:
return $this->parseEnumCaseDeclaration($parentNode);

case TokenKind::ConstKeyword:
return $this->parseClassConstDeclaration($parentNode, $modifiers);

case TokenKind::FunctionKeyword:
return $this->parseMethodDeclaration($parentNode, $modifiers);

case TokenKind::QuestionToken:
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration(
$parentNode,
$modifiers,
$this->eat1(TokenKind::QuestionToken)
);
case TokenKind::VariableName:
return $this->parsePropertyDeclaration($parentNode, $modifiers);

case TokenKind::UseKeyword:
return $this->parseTraitUseClause($parentNode);

case TokenKind::AttributeToken:
return $this->parseAttributeStatement($parentNode);

default:
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration($parentNode, $modifiers);
}
};
}


/**
* @param Node $parentNode
* @param Token[] $modifiers
Expand Down
5 changes: 5 additions & 0 deletions tests/cases/parser81/enums8.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php
class X {
// Cases are only allowed in enums. But this is a compile-time error, not a parse error
case F = 1;
}
1 change: 1 addition & 0 deletions tests/cases/parser81/enums8.php.diag
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Loading