Fix a PHP notice generating a scoped property access expression

And add a unit test
This commit is contained in:
Tyson Andre 2018-09-23 14:51:41 -04:00
Родитель 7ea773435b
Коммит 9463cfc86b
4 изменённых файлов: 241 добавлений и 7 удалений

Просмотреть файл

@ -1236,7 +1236,7 @@ class Parser {
$node->addElement($delimeterToken);
}
$token = $this->getCurrentToken();
// TODO ERROR CASE - no delimeter, but a param follows
// TODO ERROR CASE - no delimiter, but a param follows
} while ($delimeterToken !== null);
@ -2446,7 +2446,7 @@ class Parser {
return $expression;
}
if ($tokenKind === TokenKind::ColonColonToken) {
$expression = $this->parseScopedPropertyAccessExpression($expression);
$expression = $this->parseScopedPropertyAccessExpression($expression, null);
return $this->parsePostfixExpressionRest($expression);
}
@ -2594,12 +2594,18 @@ class Parser {
return $memberAccessExpression;
}
private function parseScopedPropertyAccessExpression($expression):ScopedPropertyAccessExpression {
/**
* @param Node|null $expression
* @param Node|null $fallbackParentNode (Workaround for the invalid AST `use TraitName::foo as ::x`)
*/
private function parseScopedPropertyAccessExpression($expression, $fallbackParentNode): ScopedPropertyAccessExpression {
$scopedPropertyAccessExpression = new ScopedPropertyAccessExpression();
$scopedPropertyAccessExpression->parent = $expression->parent;
$expression->parent = $scopedPropertyAccessExpression;
$scopedPropertyAccessExpression->parent = $expression->parent ?? $fallbackParentNode;
if ($expression instanceof Node) {
$expression->parent = $scopedPropertyAccessExpression;
$scopedPropertyAccessExpression->scopeResolutionQualifier = $expression; // TODO ensure always a Node
}
$scopedPropertyAccessExpression->scopeResolutionQualifier = $expression; // TODO ensure always a Node
$scopedPropertyAccessExpression->doubleColon = $this->eat1(TokenKind::ColonColonToken);
$scopedPropertyAccessExpression->memberName = $this->parseMemberName($scopedPropertyAccessExpression);
@ -3036,7 +3042,7 @@ class Parser {
private function parseQualifiedNameOrScopedPropertyAccessExpression($parentNode) {
$qualifiedNameOrScopedProperty = $this->parseQualifiedName($parentNode);
if ($this->getCurrentToken()->kind === TokenKind::ColonColonToken) {
$qualifiedNameOrScopedProperty = $this->parseScopedPropertyAccessExpression($qualifiedNameOrScopedProperty);
$qualifiedNameOrScopedProperty = $this->parseScopedPropertyAccessExpression($qualifiedNameOrScopedProperty, $parentNode);
}
return $qualifiedNameOrScopedProperty;
}

Просмотреть файл

@ -0,0 +1,7 @@
<?php
class A295 {
use T1, T2 {
T1::foo as ::xyz insteadof T2; // This is invalid, but parser invariants should hold
}
}

Просмотреть файл

@ -0,0 +1,32 @@
[
{
"kind": 0,
"message": "'}' expected.",
"start": 61,
"length": 0
},
{
"kind": 0,
"message": "Unexpected 'insteadof'",
"start": 62,
"length": 9
},
{
"kind": 0,
"message": "'}' expected.",
"start": 71,
"length": 0
},
{
"kind": 0,
"message": "Unexpected '}'",
"start": 135,
"length": 1
},
{
"kind": 0,
"message": "Unexpected '}'",
"start": 137,
"length": 1
}
]

Просмотреть файл

@ -0,0 +1,189 @@
{
"SourceFileNode": {
"statementList": [
{
"InlineHtml": {
"scriptSectionEndTag": null,
"text": null,
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"ClassDeclaration": {
"abstractOrFinalModifier": null,
"classKeyword": {
"kind": "ClassKeyword",
"textLength": 5
},
"name": {
"kind": "Name",
"textLength": 4
},
"classBaseClause": null,
"classInterfaceClause": null,
"classMembers": {
"ClassMembersNode": {
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"classMemberDeclarations": [
{
"TraitUseClause": {
"useKeyword": {
"kind": "UseKeyword",
"textLength": 3
},
"traitNameList": {
"QualifiedNameList": {
"children": [
{
"QualifiedName": {
"globalSpecifier": null,
"relativeSpecifier": null,
"nameParts": [
{
"kind": "Name",
"textLength": 2
}
]
}
},
{
"kind": "CommaToken",
"textLength": 1
},
{
"QualifiedName": {
"globalSpecifier": null,
"relativeSpecifier": null,
"nameParts": [
{
"kind": "Name",
"textLength": 2
}
]
}
}
]
}
},
"semicolonOrOpenBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"traitSelectAndAliasClauses": {
"TraitSelectOrAliasClauseList": {
"children": [
{
"TraitSelectOrAliasClause": {
"name": {
"ScopedPropertyAccessExpression": {
"scopeResolutionQualifier": {
"QualifiedName": {
"globalSpecifier": null,
"relativeSpecifier": null,
"nameParts": [
{
"kind": "Name",
"textLength": 2
}
]
}
},
"doubleColon": {
"kind": "ColonColonToken",
"textLength": 2
},
"memberName": {
"kind": "Name",
"textLength": 3
}
}
},
"asOrInsteadOfKeyword": {
"kind": "AsKeyword",
"textLength": 2
},
"modifiers": [],
"targetName": {
"ScopedPropertyAccessExpression": {
"scopeResolutionQualifier": null,
"doubleColon": {
"kind": "ColonColonToken",
"textLength": 2
},
"memberName": {
"kind": "Name",
"textLength": 3
}
}
},
"remainingTargetNames": []
}
}
]
}
},
"closeBrace": {
"error": "MissingToken",
"kind": "CloseBraceToken",
"textLength": 0
}
}
},
{
"error": "SkippedToken",
"kind": "InsteadOfKeyword",
"textLength": 9
}
],
"closeBrace": {
"error": "MissingToken",
"kind": "CloseBraceToken",
"textLength": 0
}
}
}
}
},
{
"ExpressionStatement": {
"expression": {
"QualifiedName": {
"globalSpecifier": null,
"relativeSpecifier": null,
"nameParts": [
{
"kind": "Name",
"textLength": 2
}
]
}
},
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
},
{
"error": "SkippedToken",
"kind": "CloseBraceToken",
"textLength": 1
},
{
"error": "SkippedToken",
"kind": "CloseBraceToken",
"textLength": 1
}
],
"endOfFileToken": {
"kind": "EndOfFileToken",
"textLength": 0
}
}
}