Fixes #189 : Fix parsing edge cases in yield statements
Update tests diagnostics and expected generated ASTs For backwards compatibility, continue to make `yield from expr` have an ArrayElement as a child node. To maintain the invariant that all Nodes must have at least one child, make $yieldExpression->arrayElement be null when parsing `yield;` Aside: `yield &$a;` is (and should be) parsed as the binary operator `(yield) & ($a)`. This is surprising, but makes sense, being the only sane parse tree. - Add a unit test that `yield & &$a;` is invalid. There is no way to parse that. Verified with the PHP module nikic/php-ast ```php var_export(ast_dump( ast\parse_code('<?php function test() { yield & $x; }', 50) )); ```
This commit is contained in:
Родитель
faca5af49c
Коммит
816ec34fb6
|
@ -226,7 +226,7 @@ public function getEndPosition ( )
|
|||
> TODO: add doc comment
|
||||
|
||||
```php
|
||||
public static function getTokenKindNameFromValue ( $kindName )
|
||||
public static function getTokenKindNameFromValue ( $kind )
|
||||
```
|
||||
### Token::jsonSerialize
|
||||
> TODO: add doc comment
|
||||
|
|
|
@ -2098,7 +2098,23 @@ class Parser {
|
|||
TokenKind::YieldFromKeyword,
|
||||
TokenKind::YieldKeyword
|
||||
);
|
||||
$yieldExpression->arrayElement = $this->parseArrayElement($yieldExpression);
|
||||
if ($yieldExpression->yieldOrYieldFromKeyword->kind === TokenKind::YieldFromKeyword) {
|
||||
// Don't use parseArrayElement. E.g. `yield from &$varName` or `yield from $key => $varName` are both syntax errors
|
||||
$arrayElement = new ArrayElement();
|
||||
$arrayElement->parent = $yieldExpression;
|
||||
$arrayElement->elementValue = $this->parseExpression($arrayElement);
|
||||
$yieldExpression->arrayElement = $arrayElement;
|
||||
} else {
|
||||
// This is always an ArrayElement for backwards compatibilitiy.
|
||||
// TODO: Can this be changed to a non-ArrayElement in a future release?
|
||||
if ($this->isExpressionStart($this->getCurrentToken())) {
|
||||
// Both `yield expr;` and `yield;` are possible.
|
||||
$yieldExpression->arrayElement = $this->parseArrayElement($yieldExpression);
|
||||
} else {
|
||||
$yieldExpression->arrayElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $yieldExpression;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
// TODO `return yield` should fail
|
||||
// `return yield;` is valid code in PHP 7 (invalid in PHP 5),
|
||||
// since generators can also return values. It is parsed as `return (yield);`
|
||||
function gen() {
|
||||
return yield;
|
||||
}
|
||||
|
|
|
@ -1,8 +1 @@
|
|||
[
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "'Expression' expected.",
|
||||
"start": 75,
|
||||
"length": 0
|
||||
}
|
||||
]
|
||||
[]
|
|
@ -53,18 +53,7 @@
|
|||
"kind": "YieldKeyword",
|
||||
"textLength": 5
|
||||
},
|
||||
"arrayElement": {
|
||||
"ArrayElement": {
|
||||
"elementKey": null,
|
||||
"arrowToken": null,
|
||||
"byRef": null,
|
||||
"elementValue": {
|
||||
"error": "MissingToken",
|
||||
"kind": "Expression",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
"arrayElement": null
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
// TODO should fail
|
||||
// This fails with "'Expression' expected."
|
||||
function gen() {
|
||||
return yield from;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{
|
||||
"kind": 0,
|
||||
"message": "'Expression' expected.",
|
||||
"start": 65,
|
||||
"start": 89,
|
||||
"length": 0
|
||||
}
|
||||
]
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
// should fail
|
||||
// should not fail.
|
||||
// This is parsed as the binary bitwise and operator (yield) & ($a);
|
||||
function gen() {
|
||||
yield &$a;
|
||||
}
|
||||
|
|
|
@ -44,27 +44,26 @@
|
|||
{
|
||||
"ExpressionStatement": {
|
||||
"expression": {
|
||||
"YieldExpression": {
|
||||
"yieldOrYieldFromKeyword": {
|
||||
"kind": "YieldKeyword",
|
||||
"textLength": 5
|
||||
},
|
||||
"arrayElement": {
|
||||
"ArrayElement": {
|
||||
"elementKey": null,
|
||||
"arrowToken": null,
|
||||
"byRef": {
|
||||
"kind": "AmpersandToken",
|
||||
"textLength": 1
|
||||
"BinaryExpression": {
|
||||
"leftOperand": {
|
||||
"YieldExpression": {
|
||||
"yieldOrYieldFromKeyword": {
|
||||
"kind": "YieldKeyword",
|
||||
"textLength": 5
|
||||
},
|
||||
"elementValue": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
"arrayElement": null
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"kind": "AmpersandToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"rightOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,8 @@
|
|||
[]
|
||||
[
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "'Expression' expected.",
|
||||
"start": 53,
|
||||
"length": 0
|
||||
}
|
||||
]
|
|
@ -53,16 +53,26 @@
|
|||
"ArrayElement": {
|
||||
"elementKey": null,
|
||||
"arrowToken": null,
|
||||
"byRef": {
|
||||
"kind": "AmpersandToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"byRef": null,
|
||||
"elementValue": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
"BinaryExpression": {
|
||||
"leftOperand": {
|
||||
"error": "MissingToken",
|
||||
"kind": "Expression",
|
||||
"textLength": 0
|
||||
},
|
||||
"operator": {
|
||||
"kind": "AmpersandToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"rightOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
// This is invalid. But (yield) & ($x) would be valid in PHP 7.
|
||||
function example($x) {
|
||||
yield & &$x;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "'Expression' expected.",
|
||||
"start": 105,
|
||||
"length": 0
|
||||
}
|
||||
]
|
|
@ -0,0 +1,125 @@
|
|||
{
|
||||
"SourceFileNode": {
|
||||
"statementList": [
|
||||
{
|
||||
"InlineHtml": {
|
||||
"scriptSectionEndTag": null,
|
||||
"text": null,
|
||||
"scriptSectionStartTag": {
|
||||
"kind": "ScriptSectionStartTag",
|
||||
"textLength": 6
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"FunctionDeclaration": {
|
||||
"functionKeyword": {
|
||||
"kind": "FunctionKeyword",
|
||||
"textLength": 8
|
||||
},
|
||||
"byRefToken": null,
|
||||
"name": {
|
||||
"kind": "Name",
|
||||
"textLength": 7
|
||||
},
|
||||
"openParen": {
|
||||
"kind": "OpenParenToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"parameters": {
|
||||
"ParameterDeclarationList": {
|
||||
"children": [
|
||||
{
|
||||
"Parameter": {
|
||||
"questionToken": null,
|
||||
"typeDeclaration": null,
|
||||
"byRefToken": null,
|
||||
"dotDotDotToken": null,
|
||||
"variableName": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
},
|
||||
"equalsToken": null,
|
||||
"default": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"closeParen": {
|
||||
"kind": "CloseParenToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"colonToken": null,
|
||||
"questionToken": null,
|
||||
"returnType": null,
|
||||
"compoundStatementOrSemicolon": {
|
||||
"CompoundStatementNode": {
|
||||
"openBrace": {
|
||||
"kind": "OpenBraceToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"statements": [
|
||||
{
|
||||
"ExpressionStatement": {
|
||||
"expression": {
|
||||
"BinaryExpression": {
|
||||
"leftOperand": {
|
||||
"BinaryExpression": {
|
||||
"leftOperand": {
|
||||
"YieldExpression": {
|
||||
"yieldOrYieldFromKeyword": {
|
||||
"kind": "YieldKeyword",
|
||||
"textLength": 5
|
||||
},
|
||||
"arrayElement": null
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"kind": "AmpersandToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"rightOperand": {
|
||||
"error": "MissingToken",
|
||||
"kind": "Expression",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"kind": "AmpersandToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"rightOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
"kind": "SemicolonToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"closeBrace": {
|
||||
"kind": "CloseBraceToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"endOfFileToken": {
|
||||
"kind": "EndOfFileToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
// This is parsed as (yield) && ($x).
|
||||
function example($x) {
|
||||
yield &&$x;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -0,0 +1,112 @@
|
|||
{
|
||||
"SourceFileNode": {
|
||||
"statementList": [
|
||||
{
|
||||
"InlineHtml": {
|
||||
"scriptSectionEndTag": null,
|
||||
"text": null,
|
||||
"scriptSectionStartTag": {
|
||||
"kind": "ScriptSectionStartTag",
|
||||
"textLength": 6
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"FunctionDeclaration": {
|
||||
"functionKeyword": {
|
||||
"kind": "FunctionKeyword",
|
||||
"textLength": 8
|
||||
},
|
||||
"byRefToken": null,
|
||||
"name": {
|
||||
"kind": "Name",
|
||||
"textLength": 7
|
||||
},
|
||||
"openParen": {
|
||||
"kind": "OpenParenToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"parameters": {
|
||||
"ParameterDeclarationList": {
|
||||
"children": [
|
||||
{
|
||||
"Parameter": {
|
||||
"questionToken": null,
|
||||
"typeDeclaration": null,
|
||||
"byRefToken": null,
|
||||
"dotDotDotToken": null,
|
||||
"variableName": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
},
|
||||
"equalsToken": null,
|
||||
"default": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"closeParen": {
|
||||
"kind": "CloseParenToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"colonToken": null,
|
||||
"questionToken": null,
|
||||
"returnType": null,
|
||||
"compoundStatementOrSemicolon": {
|
||||
"CompoundStatementNode": {
|
||||
"openBrace": {
|
||||
"kind": "OpenBraceToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"statements": [
|
||||
{
|
||||
"ExpressionStatement": {
|
||||
"expression": {
|
||||
"BinaryExpression": {
|
||||
"leftOperand": {
|
||||
"YieldExpression": {
|
||||
"yieldOrYieldFromKeyword": {
|
||||
"kind": "YieldKeyword",
|
||||
"textLength": 5
|
||||
},
|
||||
"arrayElement": null
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"kind": "AmpersandAmpersandToken",
|
||||
"textLength": 2
|
||||
},
|
||||
"rightOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
"kind": "SemicolonToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"closeBrace": {
|
||||
"kind": "CloseBraceToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"endOfFileToken": {
|
||||
"kind": "EndOfFileToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
// TODO technically should fail
|
||||
// Fails with the message "';' expected.", "Unexpected '=>'"
|
||||
function gen() {
|
||||
yield from 1 => 2;
|
||||
}
|
|
@ -1 +1,14 @@
|
|||
[]
|
||||
[
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "';' expected.",
|
||||
"start": 101,
|
||||
"length": 0
|
||||
},
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "Unexpected '=>'",
|
||||
"start": 102,
|
||||
"length": 2
|
||||
}
|
||||
]
|
|
@ -51,18 +51,8 @@
|
|||
},
|
||||
"arrayElement": {
|
||||
"ArrayElement": {
|
||||
"elementKey": {
|
||||
"NumericLiteral": {
|
||||
"children": {
|
||||
"kind": "IntegerLiteralToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"arrowToken": {
|
||||
"kind": "DoubleArrowToken",
|
||||
"textLength": 2
|
||||
},
|
||||
"elementKey": null,
|
||||
"arrowToken": null,
|
||||
"byRef": null,
|
||||
"elementValue": {
|
||||
"NumericLiteral": {
|
||||
|
@ -76,6 +66,28 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
"error": "MissingToken",
|
||||
"kind": "SemicolonToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"error": "SkippedToken",
|
||||
"kind": "DoubleArrowToken",
|
||||
"textLength": 2
|
||||
},
|
||||
{
|
||||
"ExpressionStatement": {
|
||||
"expression": {
|
||||
"NumericLiteral": {
|
||||
"children": {
|
||||
"kind": "IntegerLiteralToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
"kind": "SemicolonToken",
|
||||
"textLength": 1
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
// TODO technically should fail
|
||||
// Fails with the messages "';' expected.", "Unexpected '=>'"
|
||||
function gen() {
|
||||
yield from $i => $a;
|
||||
}
|
|
@ -1 +1,14 @@
|
|||
[]
|
||||
[
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "';' expected.",
|
||||
"start": 103,
|
||||
"length": 0
|
||||
},
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "Unexpected '=>'",
|
||||
"start": 104,
|
||||
"length": 2
|
||||
}
|
||||
]
|
|
@ -51,19 +51,8 @@
|
|||
},
|
||||
"arrayElement": {
|
||||
"ArrayElement": {
|
||||
"elementKey": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"arrowToken": {
|
||||
"kind": "DoubleArrowToken",
|
||||
"textLength": 2
|
||||
},
|
||||
"elementKey": null,
|
||||
"arrowToken": null,
|
||||
"byRef": null,
|
||||
"elementValue": {
|
||||
"Variable": {
|
||||
|
@ -78,6 +67,29 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
"error": "MissingToken",
|
||||
"kind": "SemicolonToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"error": "SkippedToken",
|
||||
"kind": "DoubleArrowToken",
|
||||
"textLength": 2
|
||||
},
|
||||
{
|
||||
"ExpressionStatement": {
|
||||
"expression": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
"kind": "SemicolonToken",
|
||||
"textLength": 1
|
||||
|
|
|
@ -1,8 +1 @@
|
|||
[
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "'Expression' expected.",
|
||||
"start": 33,
|
||||
"length": 0
|
||||
}
|
||||
]
|
||||
[]
|
|
@ -49,18 +49,7 @@
|
|||
"kind": "YieldKeyword",
|
||||
"textLength": 5
|
||||
},
|
||||
"arrayElement": {
|
||||
"ArrayElement": {
|
||||
"elementKey": null,
|
||||
"arrowToken": null,
|
||||
"byRef": null,
|
||||
"elementValue": {
|
||||
"error": "MissingToken",
|
||||
"kind": "Expression",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
"arrayElement": null
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
// TODO should fail
|
||||
// Fails with the message "'Expression' expected."
|
||||
function gen() {
|
||||
yield from;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{
|
||||
"kind": 0,
|
||||
"message": "'Expression' expected.",
|
||||
"start": 58,
|
||||
"start": 89,
|
||||
"length": 0
|
||||
}
|
||||
]
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
// TODO `return yield` should fail
|
||||
// TODO `return yield` should fail in php5
|
||||
function gen() {
|
||||
return yield a();
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче