Make parser stricter for for statements and expressions (PY-36478)

Now it more follows original python grammar.

GitOrigin-RevId: 199f30b819c85a4eba5cab9b3f67fbf1bb7f18b6
This commit is contained in:
Semyon Proshev
2020-11-24 17:35:00 +00:00
committed by intellij-monorepo-bot
parent 9335c066f5
commit 48a952a327
8 changed files with 137 additions and 4 deletions
@@ -217,6 +217,7 @@ ANN.cannot.assign.to.debug=Cannot assign to __debug__
ANN.unparenthesized.assignment.expression.statement=Unparenthesized assignment expressions are prohibited at the top level of an expression statement
ANN.unparenthesized.assignment.expression.value=Unparenthesized assignment expressions are prohibited at the top level of the right hand side of an assignment statement
ANN.assignment.expressions.within.a.comprehension.cannot.be.used.in.a.class.body=Assignment expressions within a comprehension cannot be used in a class body
ANN.assignment.expression.as.a.target=Assignment expression cannot be used as a target here
ANN.ignore.errors.like.this=Ignore errors like this
ANN.function.cannot.be.async=function \"{0}\" cannot be async
ANN.python.does.not.support.yield.from.inside.async.functions=Python does not support 'yield from' inside async functions
@@ -264,7 +264,7 @@ public class ExpressionParsing extends Parsing {
assertCurrentToken(PyTokenTypes.FOR_KEYWORD);
while (true) {
myBuilder.advanceLexer();
parseExpression(true, true);
parseStarTargets();
parseComprehensionRange(exprType == PyElementTypes.GENERATOR_EXPRESSION);
while (myBuilder.getTokenType() == PyTokenTypes.IF_KEYWORD) {
myBuilder.advanceLexer();
@@ -284,6 +284,32 @@ public class ExpressionParsing extends Parsing {
expr.done(exprType);
}
public boolean parseStarTargets() {
SyntaxTreeBuilder.Marker expr = myBuilder.mark();
if (!parseStarExpression(true)) {
myBuilder.error(message("PARSE.expected.expression"));
expr.drop();
return false;
}
if (myBuilder.getTokenType() == PyTokenTypes.COMMA) {
while (myBuilder.getTokenType() == PyTokenTypes.COMMA) {
myBuilder.advanceLexer();
SyntaxTreeBuilder.Marker expr2 = myBuilder.mark();
if (!parseStarExpression(true)) {
myBuilder.error(message("PARSE.expected.expression"));
expr2.rollbackTo();
break;
}
expr2.drop();
}
expr.done(PyElementTypes.TUPLE_EXPRESSION);
}
else {
expr.drop();
}
return true;
}
protected void parseComprehensionRange(boolean generatorExpression) {
checkMatches(PyTokenTypes.IN_KEYWORD, message("PARSE.expected.in"));
boolean result;
@@ -670,7 +670,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper {
protected void parseForPart() {
final SyntaxTreeBuilder.Marker forPart = myBuilder.mark();
myBuilder.advanceLexer();
getExpressionParser().parseExpression(true, true);
getExpressionParser().parseStarTargets();
checkMatches(PyTokenTypes.IN_KEYWORD, PyPsiBundle.message("PARSE.expected.in"));
getExpressionParser().parseExpression();
parseColonAndSuite();
@@ -24,9 +24,11 @@ import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.sdk.PythonSdkUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.jetbrains.python.PyPsiBundle.message;
@@ -79,6 +81,7 @@ public class AssignTargetAnnotator extends PyAnnotator {
PyExpression target = node.getForPart().getTarget();
if (target != null) {
target.accept(new ExprVisitor(Operation.For));
checkTargetIsNotAssignmentExpression(target);
}
}
@@ -110,6 +113,20 @@ public class AssignTargetAnnotator extends PyAnnotator {
}
}
@Override
public void visitPyComprehensionElement(@NotNull PyComprehensionElement node) {
node.getForComponents().forEach(it -> checkTargetIsNotAssignmentExpression(it.getIteratorVariable()));
}
private void checkTargetIsNotAssignmentExpression(@Nullable PyExpression expression) {
if (PyPsiUtils.flattenParens(expression) instanceof PyAssignmentExpression) {
getHolder()
.newAnnotation(HighlightSeverity.ERROR, PyBundle.message("ANN.assignment.expression.as.a.target"))
.range(expression)
.create();
}
}
private class ExprVisitor extends PyElementVisitor {
private final Operation myOp;
private final @Nls String DELETING_NONE = message("ANN.deleting.none");
@@ -0,0 +1,4 @@
[x1 for <error descr="Assignment expression cannot be used as a target here">(x1 := 2)</error> in (1, 2, 3)]
for <error descr="Assignment expression cannot be used as a target here">(x1 := 2)</error> in (1, 2, 3):
pass
@@ -12,4 +12,7 @@
(b := -)
(x := )
x = (b[j] := z) = 'spam' # z is a reference
x = (b[j] := z) = 'spam' # z is a reference
[x1 for x1 := 2 in (1, 2, 3)]
[x1 for (x1 := 2) in (1, 2, 3)]
@@ -142,4 +142,81 @@ PyFile:InvalidAssignmentExpressions.py
PyStringLiteralExpression: spam
PsiElement(Py:SINGLE_QUOTED_STRING)(''spam'')
PsiWhiteSpace(' ')
PsiComment(Py:END_OF_LINE_COMMENT)('# z is a reference')
PsiComment(Py:END_OF_LINE_COMMENT)('# z is a reference')
PsiWhiteSpace('\n\n')
PyAssignmentExpression
PyListCompExpression
PsiElement(Py:LBRACKET)('[')
PyReferenceExpression: x1
PsiElement(Py:IDENTIFIER)('x1')
PsiWhiteSpace(' ')
PsiElement(Py:FOR_KEYWORD)('for')
PsiWhiteSpace(' ')
PyTargetExpression: x1
PsiElement(Py:IDENTIFIER)('x1')
PsiErrorElement:'in' expected
<empty list>
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyBinaryExpression
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('2')
PsiWhiteSpace(' ')
PsiElement(Py:IN_KEYWORD)('in')
PsiWhiteSpace(' ')
PyParenthesizedExpression
PsiElement(Py:LPAR)('(')
PyTupleExpression
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('1')
PsiElement(Py:COMMA)(',')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('2')
PsiElement(Py:COMMA)(',')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('3')
PsiElement(Py:RPAR)(')')
PsiElement(Py:RBRACKET)(']')
PsiErrorElement:Statement expected, found Py:LBRACKET
<empty list>
PsiWhiteSpace('\n')
PyExpressionStatement
PyListCompExpression
PsiElement(Py:LBRACKET)('[')
PyReferenceExpression: x1
PsiElement(Py:IDENTIFIER)('x1')
PsiWhiteSpace(' ')
PsiElement(Py:FOR_KEYWORD)('for')
PsiWhiteSpace(' ')
PyParenthesizedExpression
PsiElement(Py:LPAR)('(')
PyAssignmentExpression
PyTargetExpression: x1
PsiElement(Py:IDENTIFIER)('x1')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('2')
PsiElement(Py:RPAR)(')')
PsiWhiteSpace(' ')
PsiElement(Py:IN_KEYWORD)('in')
PsiWhiteSpace(' ')
PyParenthesizedExpression
PsiElement(Py:LPAR)('(')
PyTupleExpression
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('1')
PsiElement(Py:COMMA)(',')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('2')
PsiElement(Py:COMMA)(',')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('3')
PsiElement(Py:RPAR)(')')
PsiElement(Py:RBRACKET)(']')
@@ -488,6 +488,11 @@ public class PythonHighlightingTest extends PyTestCase {
doTest(LanguageLevel.PYTHON38, false, false);
}
// PY-36478
public void testAssignmentExpressionAsATarget() {
doTest(LanguageLevel.getLatest(), false, false);
}
@NotNull
private static EditorColorsScheme createTemporaryColorScheme() {
EditorColorsManager manager = EditorColorsManager.getInstance();