Skip to content
Merged
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
29 changes: 24 additions & 5 deletions src/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -1068,10 +1068,9 @@ private function parsePrimaryExpression($parentNode) {
// anonymous-function-creation-expression
case TokenKind::StaticKeyword:
// handle `static::`, `static(`, `new static;`, `instanceof static`
if (($this->lookahead([TokenKind::ColonColonToken, TokenKind::OpenParenToken])) ||
(!$this->lookahead([TokenKind::FunctionKeyword, TokenKind::FnKeyword]))
) {
return $this->parseQualifiedName($parentNode);
if ((!$this->lookahead([TokenKind::FunctionKeyword, TokenKind::FnKeyword]))) {
// TODO: Should this check the parent type to reject `$x = static;`, `$x = static();`, etc.
return $this->parseStaticQualifiedName($parentNode);
}
// Could be `static function` anonymous function creation expression, so flow through
case TokenKind::FunctionKeyword:
Expand Down Expand Up @@ -1370,6 +1369,21 @@ private function isQualifiedNameStartForCatchFn() {
};
}

/**
* @return QualifiedName
*/
private function parseStaticQualifiedName($parentNode) {
$node = new QualifiedName();
$token = $this->eat(TokenKind::StaticKeyword);
$token->kind = TokenKind::Name;
$node->parent = $parentNode;
$node->nameParts = [$token];
return $node;
}

/**
* @return QualifiedName|null - returns null for invalid qualified names such as `static\` (use parseStaticQualifiedName for that)
*/
private function parseQualifiedName($parentNode) {
return ($this->parseQualifiedNameFn())($parentNode);
}
Expand All @@ -1388,7 +1402,9 @@ private function parseQualifiedNameFn() {
DelimitedList\QualifiedNameParts::class,
TokenKind::BackslashToken,
function ($token) {
// a\static() <- VALID
// a\static() <- INVALID (but not checked for right now)
// new a\static() <- INVALID
// new static() <- VALID
// a\static\b <- INVALID
// a\function <- INVALID
// a\true\b <-VALID
Expand Down Expand Up @@ -1485,6 +1501,9 @@ private function parseNamedLabelStatement($parentNode) {
return $namedLabelStatement;
}

/**
* @param int|int[] ...$expectedKinds an array of one or more kinds/sets of allowed kinds in each position
*/
private function lookahead(...$expectedKinds) : bool {
$startPos = $this->lexer->getCurrentPosition();
$startToken = $this->token;
Expand Down
1 change: 1 addition & 0 deletions tests/cases/parser/binaryExpression20.php
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?php \&static\
14 changes: 14 additions & 0 deletions tests/cases/parser/binaryExpression20.php.diag
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
"kind": 0,
"message": "';' expected.",
"start": 14,
"length": 0
},
{
"kind": 0,
"message": "';' expected.",
"start": 15,
"length": 0
}
]
78 changes: 78 additions & 0 deletions tests/cases/parser/binaryExpression20.php.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{
"SourceFileNode": {
"statementList": [
{
"InlineHtml": {
"scriptSectionEndTag": null,
"text": null,
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"ExpressionStatement": {
"expression": {
"BinaryExpression": {
"leftOperand": {
"QualifiedName": {
"globalSpecifier": {
"kind": "BackslashToken",
"textLength": 1
},
"relativeSpecifier": null,
"nameParts": []
}
},
"operator": {
"kind": "AmpersandToken",
"textLength": 1
},
"rightOperand": {
"QualifiedName": {
"globalSpecifier": null,
"relativeSpecifier": null,
"nameParts": [
{
"kind": "Name",
"textLength": 6
}
]
}
}
}
},
"semicolon": {
"error": "MissingToken",
"kind": "SemicolonToken",
"textLength": 0
}
}
},
{
"ExpressionStatement": {
"expression": {
"QualifiedName": {
"globalSpecifier": {
"kind": "BackslashToken",
"textLength": 1
},
"relativeSpecifier": null,
"nameParts": []
}
},
"semicolon": {
"error": "MissingToken",
"kind": "SemicolonToken",
"textLength": 0
}
}
}
],
"endOfFileToken": {
"kind": "EndOfFileToken",
"textLength": 0
}
}
}