Add parsing for assignment expressions (PEP 572) (PY-33886)

Highlight expressions that are invalid by grammar or unacceptable in runtime
Highlight expressions in pythons < 3.8

GitOrigin-RevId: 89acec9db5b3a931da31c33778185e147240ec01
This commit is contained in:
Semyon Proshev
2019-07-02 06:52:16 +03:00
committed by intellij-monorepo-bot
parent fccb1b8b79
commit ccc2156cf9
21 changed files with 1123 additions and 589 deletions
File diff suppressed because it is too large Load Diff
@@ -141,13 +141,14 @@ public class PyTokenTypes {
public static final PyElementType GTGTEQ = new PyElementType("GTGTEQ");// >>=
public static final PyElementType EXPEQ = new PyElementType("EXPEQ");// **=
public static final PyElementType RARROW = new PyElementType("RARROW");// ->
public static final PyElementType COLONEQ = new PyElementType("COLONEQ");// :=
public static final TokenSet OPERATIONS = TokenSet.create(
PLUS, MINUS, MULT, AT, EXP, DIV, FLOORDIV, PERC, LTLT, GTGT, AND, OR,
XOR, TILDE, LT, GT, LE, GE, EQEQ, NE, NE_OLD, AT, COLON, TICK, EQ,
PLUSEQ, MINUSEQ,
MULTEQ, ATEQ, DIVEQ, FLOORDIVEQ, PERCEQ, ANDEQ, OREQ, XOREQ, LTLTEQ, GTGTEQ,
EXPEQ);
EXPEQ, COLONEQ);
public static final TokenSet COMPARISON_OPERATIONS = TokenSet.create(
LT, GT, EQEQ, GE, LE, NE, NE_OLD, IN_KEYWORD, IS_KEYWORD, NOT_KEYWORD);
@@ -0,0 +1,26 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.psi;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents assignment expressions introduced in Python 3.8 (PEP 572).
*/
@ApiStatus.NonExtendable
@ApiStatus.AvailableSince("2019.2")
public interface PyAssignmentExpression extends PyExpression {
/**
* @return LHS of an expression (before :=)
*/
@NotNull
PyTargetExpression getTarget();
/**
* @return RHS of an expression (after :=)
*/
@Nullable
PyExpression getAssignedValue();
}
@@ -304,4 +304,8 @@ public class PyElementVisitor extends PsiElementVisitor {
public void visitPySingleStarParameter(PySingleStarParameter node) {
visitPyElement(node);
}
public void visitPyAssignmentExpression(PyAssignmentExpression node) {
visitPyExpression(node);
}
}
@@ -40,7 +40,7 @@ public interface PyElementTypes extends PyStubElementTypes {
PyElementType DEL_STATEMENT = new PyElementType("DEL_STATEMENT", PyDelStatementImpl::new);
PyElementType EXEC_STATEMENT = new PyElementType("EXEC_STATEMENT", PyExecStatementImpl::new);
PyElementType FOR_STATEMENT = new PyElementType("FOR_STATEMENT", PyForStatementImpl::new);
PyElementType TYPE_DECLARATION_STATEMENT = new PyElementType("TYPE_DECLARATION_STATEMENT", PyTypeDeclarationStatementImpl::new);
PyElementType TYPE_DECLARATION_STATEMENT = new PyElementType("TYPE_DECLARATION_STATEMENT", PyTypeDeclarationStatementImpl::new);
PyElementType GLOBAL_STATEMENT = new PyElementType("GLOBAL_STATEMENT", PyGlobalStatementImpl::new);
PyElementType IF_STATEMENT = new PyElementType("IF_STATEMENT", PyIfStatementImpl::new);
@@ -88,6 +88,7 @@ public interface PyElementTypes extends PyStubElementTypes {
PyElementType YIELD_EXPRESSION = new PyElementType("YIELD_EXPRESSION", PyYieldExpressionImpl::new);
PyElementType STAR_EXPRESSION = new PyElementType("STAR_EXPRESSION", PyStarExpressionImpl::new);
PyElementType DOUBLE_STAR_EXPRESSION = new PyElementType("DOUBLE_STAR_EXPRESSION", PyDoubleStarExpressionImpl::new);
PyElementType ASSIGNMENT_EXPRESSION = new PyElementType("ASSIGNMENT_EXPRESSION", PyAssignmentExpressionImpl::new);
PyElementType SET_LITERAL_EXPRESSION = new PyElementType("SET_LITERAL_EXPRESSION", PySetLiteralExpressionImpl::new);
PyElementType SET_COMP_EXPRESSION = new PyElementType("SET_COMP_EXPRESSION", PySetCompExpressionImpl::new);
@@ -45,7 +45,8 @@ public class PythonTokenSetContributor extends PythonDialectsTokenSetContributor
LAMBDA_EXPRESSION, LIST_COMP_EXPRESSION, DICT_LITERAL_EXPRESSION, KEY_VALUE_EXPRESSION,
REPR_EXPRESSION, GENERATOR_EXPRESSION, CONDITIONAL_EXPRESSION, YIELD_EXPRESSION,
TARGET_EXPRESSION, NONE_LITERAL_EXPRESSION, BOOL_LITERAL_EXPRESSION,
SET_LITERAL_EXPRESSION, SET_COMP_EXPRESSION, DICT_COMP_EXPRESSION, STAR_EXPRESSION, DOUBLE_STAR_EXPRESSION);
SET_LITERAL_EXPRESSION, SET_COMP_EXPRESSION, DICT_COMP_EXPRESSION, STAR_EXPRESSION, DOUBLE_STAR_EXPRESSION,
ASSIGNMENT_EXPRESSION);
}
@NotNull
@@ -265,6 +265,7 @@ return PyTokenTypes.DOCSTRING; }
"`" { return PyTokenTypes.TICK; }
"=" { return PyTokenTypes.EQ; }
";" { return PyTokenTypes.SEMICOLON; }
":=" { return PyTokenTypes.COLONEQ; }
{FSTRING_START} { return fStringHelper.handleFStringStart(); }
@@ -21,6 +21,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyStubElementTypes;
import com.jetbrains.python.PyTokenTypes;
import org.jetbrains.annotations.Nullable;
@@ -41,7 +42,7 @@ public class ExpressionParsing extends Parsing {
final IElementType firstToken = myBuilder.getTokenType();
if (isIdentifier(myBuilder)) {
if (isTargetExpression) {
buildTokenElement(PyElementTypes.TARGET_EXPRESSION, myBuilder);
buildTokenElement(PyStubElementTypes.TARGET_EXPRESSION, myBuilder);
}
else {
buildTokenElement(getReferenceType(), myBuilder);
@@ -475,7 +476,7 @@ public class ExpressionParsing extends Parsing {
myBuilder.advanceLexer();
checkMatches(PyTokenTypes.IDENTIFIER, message("PARSE.expected.name"));
if (isTargetExpression && !recastQualifier && !atAnyOfTokens(PyTokenTypes.DOT, PyTokenTypes.LPAR, PyTokenTypes.LBRACKET)) {
expr.done(PyElementTypes.TARGET_EXPRESSION);
expr.done(PyStubElementTypes.TARGET_EXPRESSION);
}
else {
expr.done(getReferenceType());
@@ -602,12 +603,12 @@ public class ExpressionParsing extends Parsing {
while (atToken(PyTokenTypes.COMMA)) {
nextToken();
PsiBuilder.Marker sliceItemStart = myBuilder.mark();
parseTestExpression(false, false);
parseNamedTestExpression(false, false);
if (matchToken(PyTokenTypes.COLON)) {
inSlice = true;
parseTestExpression(false, false);
parseNamedTestExpression(false, false);
if (matchToken(PyTokenTypes.COLON)) {
parseTestExpression(false, false);
parseNamedTestExpression(false, false);
}
}
sliceItemStart.done(PyElementTypes.SLICE_ITEM);
@@ -733,7 +734,7 @@ public class ExpressionParsing extends Parsing {
protected boolean parseTupleExpression(boolean stopOnIn, boolean isTargetExpression, final boolean oldTest) {
PsiBuilder.Marker expr = myBuilder.mark();
boolean exprParseResult = oldTest ? parseOldTestExpression() : parseTestExpression(stopOnIn, isTargetExpression);
boolean exprParseResult = oldTest ? parseOldTestExpression() : parseNamedTestExpression(stopOnIn, isTargetExpression);
if (!exprParseResult) {
expr.drop();
return false;
@@ -742,7 +743,7 @@ public class ExpressionParsing extends Parsing {
while (myBuilder.getTokenType() == PyTokenTypes.COMMA) {
myBuilder.advanceLexer();
PsiBuilder.Marker expr2 = myBuilder.mark();
exprParseResult = oldTest ? parseOldTestExpression() : parseTestExpression(stopOnIn, isTargetExpression);
exprParseResult = oldTest ? parseOldTestExpression() : parseNamedTestExpression(stopOnIn, isTargetExpression);
if (!exprParseResult) {
expr2.rollbackTo();
break;
@@ -758,7 +759,7 @@ public class ExpressionParsing extends Parsing {
}
public boolean parseSingleExpression(boolean isTargetExpression) {
return parseTestExpression(false, isTargetExpression);
return parseNamedTestExpression(false, isTargetExpression);
}
public boolean parseOldExpression() {
@@ -768,6 +769,40 @@ public class ExpressionParsing extends Parsing {
return parseORTestExpression(false, false);
}
private boolean parseNamedTestExpression(boolean stopOnIn, boolean isTargetExpression) {
final PsiBuilder.Marker expr = myBuilder.mark();
if (isIdentifier(myBuilder) && myBuilder.lookAhead(1) == PyTokenTypes.COLONEQ) {
buildTokenElement(PyStubElementTypes.TARGET_EXPRESSION, myBuilder);
myBuilder.advanceLexer();
if (parseTestExpression(stopOnIn, isTargetExpression)) {
expr.done(PyElementTypes.ASSIGNMENT_EXPRESSION);
return true;
}
else {
expr.drop();
myBuilder.error(message("PARSE.expected.expression"));
return false;
}
}
else if (parseTestExpression(stopOnIn, isTargetExpression)) {
if (!atToken(PyTokenTypes.COLONEQ)) {
expr.drop();
return true;
}
else {
expr.error(message("PARSE.expected.identifier"));
return false;
}
}
else {
expr.drop();
return false;
}
}
private boolean parseTestExpression(boolean stopOnIn, boolean isTargetExpression) {
if (myBuilder.getTokenType() == PyTokenTypes.LAMBDA_KEYWORD) {
return parseLambdaExpression(false);
@@ -796,7 +831,7 @@ public class ExpressionParsing extends Parsing {
}
else {
myBuilder.advanceLexer();
if (!parseTestExpression(stopOnIn, isTargetExpression)) {
if (!parseNamedTestExpression(stopOnIn, isTargetExpression)) {
myBuilder.error(message("PARSE.expected.expression"));
}
}
@@ -0,0 +1,46 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.util.ObjectUtils;
import com.jetbrains.python.psi.PyAssignmentExpression;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyTargetExpression;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ApiStatus.Internal
@ApiStatus.AvailableSince("2019.2")
public class PyAssignmentExpressionImpl extends PyElementImpl implements PyAssignmentExpression {
public PyAssignmentExpressionImpl(@NotNull ASTNode astNode) {
super(astNode);
}
@NotNull
@Override
public PyTargetExpression getTarget() {
return notNullChild(ObjectUtils.tryCast(getFirstChild(), PyTargetExpression.class));
}
@Nullable
@Override
public PyExpression getAssignedValue() {
return ObjectUtils.tryCast(getLastChild(), PyExpression.class);
}
@Nullable
@Override
public PyType getType(@NotNull TypeEvalContext context, @NotNull TypeEvalContext.Key key) {
return context.getType(getTarget());
}
@Override
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyAssignmentExpression(this);
}
}
@@ -19,6 +19,8 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.jetbrains.python.PyBundle.message;
@@ -35,6 +37,9 @@ public class AssignTargetAnnotator extends PyAnnotator {
for (PyExpression expression : node.getRawTargets()) {
expression.accept(new ExprVisitor(Operation.Assign));
}
errorOnUnparenthesizedAssignmentExpression(node.getAssignedValue(),
"at the top level of the right hand side of an assignment statement");
}
@Override
@@ -74,6 +79,37 @@ public class AssignTargetAnnotator extends PyAnnotator {
}
}
@Override
public void visitPyExpressionStatement(PyExpressionStatement node) {
errorOnUnparenthesizedAssignmentExpression(node.getExpression(), "at the top level of an expression statement");
}
@Override
public void visitPyNamedParameter(PyNamedParameter node) {
errorOnUnparenthesizedAssignmentExpression(node.getDefaultValue(), "at the top level of a function default value");
}
@Override
public void visitPyKeywordArgument(PyKeywordArgument node) {
errorOnUnparenthesizedAssignmentExpression(node.getValueExpression(), "for the value of a keyword argument in a call");
}
@Override
public void visitPyLambdaExpression(PyLambdaExpression node) {
errorOnUnparenthesizedAssignmentExpression(node.getBody(), "at the top level of a lambda function");
}
@Override
public void visitPyAnnotation(PyAnnotation node) {
errorOnUnparenthesizedAssignmentExpression(node.getValue(), "as annotations for arguments, return values and assignments");
}
private void errorOnUnparenthesizedAssignmentExpression(@Nullable PyExpression expression, @NotNull String suffix) {
if (expression instanceof PyAssignmentExpression) {
getHolder().createErrorAnnotation(expression, "Unparenthesized assignment expressions are prohibited " + suffix);
}
}
private class ExprVisitor extends PyElementVisitor {
private final Operation myOp;
private final String DELETING_NONE = message("ANN.deleting.none");
@@ -702,4 +702,11 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
" not support equality signs in f-strings", equalitySignInFStringFragment.getPsi());
}
}
@Override
public void visitPyAssignmentExpression(PyAssignmentExpression node) {
super.visitPyAssignmentExpression(node);
registerForAllMatchingVersions(level -> level.isOlderThan(LanguageLevel.PYTHON38) && registerForLanguageLevel(level),
" not support assignment expressions", node);
}
}
@@ -0,0 +1,23 @@
<error descr="Unparenthesized assignment expressions are prohibited at the top level of an expression statement">y := f(x)</error> # INVALID
(y := f(x)) # Valid, though not recommended
y0 = <error descr="Unparenthesized assignment expressions are prohibited at the top level of the right hand side of an assignment statement">y1 := f(x)</error> # INVALID
y0 = (y1 := f(x)) # Valid, though discouraged
foo(x = <error descr="Unparenthesized assignment expressions are prohibited for the value of a keyword argument in a call">y := f(x)</error>) # INVALID
foo(x=(y := f(x))) # Valid, though probably confusing
def foo(answer = <error descr="Unparenthesized assignment expressions are prohibited at the top level of a function default value">p := 42</error>): # INVALID
pass
def foo(answer=(p := 42)): # Valid, though not great style
pass
def foo(answer: <error descr="Unparenthesized assignment expressions are prohibited as annotations for arguments, return values and assignments">p := 42</error> = 5): # INVALID
pass
def foo(answer: (p := 42) = 5): # Valid, but probably never useful
pass
(lambda: <error descr="Unparenthesized assignment expressions are prohibited at the top level of a lambda function">x := 1</error>) # INVALID
lambda: (x := 1) # Valid, but unlikely to be useful
(x := lambda: 1) # Valid
lambda line: (m := re.match(pattern, line)) and m.group(1) # Valid
@@ -0,0 +1,13 @@
# Handle a matched regex
if (<error descr="Python version 3.7 does not support assignment expressions">match := pattern.search(data)</error>) is not None:
pass
# A loop that can't be trivially rewritten using 2-arg iter()
while <error descr="Python version 3.7 does not support assignment expressions">chunk := file.read(8192)</error>:
pass
# Reuse a value that's expensive to compute
[<error descr="Python version 3.7 does not support assignment expressions">y := f(x)</error>, y**2, y**3]
# Share a subexpression between a comprehension filter clause and its output
filtered_data = [y for x in data if (<error descr="Python version 3.7 does not support assignment expressions">y := f(x)</error>) is not None]
@@ -0,0 +1,13 @@
# Handle a matched regex
if (<warning descr="Python version 2.6, 2.7, 3.4, 3.5, 3.6, 3.7 do not support assignment expressions">match := pattern.search(data)</warning>) is not None:
pass
# A loop that can't be trivially rewritten using 2-arg iter()
while <warning descr="Python version 2.6, 2.7, 3.4, 3.5, 3.6, 3.7 do not support assignment expressions">chunk := file.read(8192)</warning>:
pass
# Reuse a value that's expensive to compute
[<warning descr="Python version 2.6, 2.7, 3.4, 3.5, 3.6, 3.7 do not support assignment expressions">y := f(x)</warning>, y**2, y**3]
# Share a subexpression between a comprehension filter clause and its output
filtered_data = [y for x in data if (<warning descr="Python version 2.6, 2.7, 3.4, 3.5, 3.6, 3.7 do not support assignment expressions">y := f(x)</warning>) is not None]
@@ -0,0 +1,17 @@
if a := b:
pass
while c := d:
pass
[y := 2, y**2, y**3]
[y for x in data if (y := f(x))]
len(lines := [])
foo(x := 1, cat='2')
(loc := x, y) # loc will be x, not (x, y)
(px, py, pz := position) # pz will be position, px and py are references
@@ -0,0 +1,186 @@
PyFile:AssignmentExpressions.py
PyIfStatement
PyIfPartIf
PsiElement(Py:IF_KEYWORD)('if')
PsiWhiteSpace(' ')
PyAssignmentExpression
PyTargetExpression: a
PsiElement(Py:IDENTIFIER)('a')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyReferenceExpression: b
PsiElement(Py:IDENTIFIER)('b')
PsiElement(Py:COLON)(':')
PsiWhiteSpace('\n ')
PyStatementList
PyPassStatement
PsiElement(Py:PASS_KEYWORD)('pass')
PsiWhiteSpace('\n\n')
PyWhileStatement
PyWhilePart
PsiElement(Py:WHILE_KEYWORD)('while')
PsiWhiteSpace(' ')
PyAssignmentExpression
PyTargetExpression: c
PsiElement(Py:IDENTIFIER)('c')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyReferenceExpression: d
PsiElement(Py:IDENTIFIER)('d')
PsiElement(Py:COLON)(':')
PsiWhiteSpace('\n ')
PyStatementList
PyPassStatement
PsiElement(Py:PASS_KEYWORD)('pass')
PsiWhiteSpace('\n\n')
PyExpressionStatement
PyListLiteralExpression
PsiElement(Py:LBRACKET)('[')
PyAssignmentExpression
PyTargetExpression: y
PsiElement(Py:IDENTIFIER)('y')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('2')
PsiElement(Py:COMMA)(',')
PsiWhiteSpace(' ')
PyBinaryExpression
PyReferenceExpression: y
PsiElement(Py:IDENTIFIER)('y')
PsiElement(Py:EXP)('**')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('2')
PsiElement(Py:COMMA)(',')
PsiWhiteSpace(' ')
PyBinaryExpression
PyReferenceExpression: y
PsiElement(Py:IDENTIFIER)('y')
PsiElement(Py:EXP)('**')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('3')
PsiElement(Py:RBRACKET)(']')
PsiWhiteSpace('\n\n')
PyExpressionStatement
PyListCompExpression
PsiElement(Py:LBRACKET)('[')
PyReferenceExpression: y
PsiElement(Py:IDENTIFIER)('y')
PsiWhiteSpace(' ')
PsiElement(Py:FOR_KEYWORD)('for')
PsiWhiteSpace(' ')
PyTargetExpression: x
PsiElement(Py:IDENTIFIER)('x')
PsiWhiteSpace(' ')
PsiElement(Py:IN_KEYWORD)('in')
PsiWhiteSpace(' ')
PyReferenceExpression: data
PsiElement(Py:IDENTIFIER)('data')
PsiWhiteSpace(' ')
PsiElement(Py:IF_KEYWORD)('if')
PsiWhiteSpace(' ')
PyParenthesizedExpression
PsiElement(Py:LPAR)('(')
PyAssignmentExpression
PyTargetExpression: y
PsiElement(Py:IDENTIFIER)('y')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyCallExpression: f
PyReferenceExpression: f
PsiElement(Py:IDENTIFIER)('f')
PyArgumentList
PsiElement(Py:LPAR)('(')
PyReferenceExpression: x
PsiElement(Py:IDENTIFIER)('x')
PsiElement(Py:RPAR)(')')
PsiElement(Py:RPAR)(')')
PsiElement(Py:RBRACKET)(']')
PsiWhiteSpace('\n\n')
PyExpressionStatement
PyCallExpression: len
PyReferenceExpression: len
PsiElement(Py:IDENTIFIER)('len')
PyArgumentList
PsiElement(Py:LPAR)('(')
PyAssignmentExpression
PyTargetExpression: lines
PsiElement(Py:IDENTIFIER)('lines')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyListLiteralExpression
PsiElement(Py:LBRACKET)('[')
PsiElement(Py:RBRACKET)(']')
PsiElement(Py:RPAR)(')')
PsiWhiteSpace('\n\n')
PyExpressionStatement
PyCallExpression: foo
PyReferenceExpression: foo
PsiElement(Py:IDENTIFIER)('foo')
PyArgumentList
PsiElement(Py:LPAR)('(')
PyAssignmentExpression
PyTargetExpression: x
PsiElement(Py:IDENTIFIER)('x')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('1')
PsiElement(Py:COMMA)(',')
PsiWhiteSpace(' ')
PyKeywordArgumentImpl: cat
PsiElement(Py:IDENTIFIER)('cat')
PsiElement(Py:EQ)('=')
PyStringLiteralExpression: 2
PsiElement(Py:SINGLE_QUOTED_STRING)(''2'')
PsiElement(Py:RPAR)(')')
PsiWhiteSpace('\n\n')
PyExpressionStatement
PyParenthesizedExpression
PsiElement(Py:LPAR)('(')
PyTupleExpression
PyAssignmentExpression
PyTargetExpression: loc
PsiElement(Py:IDENTIFIER)('loc')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyReferenceExpression: x
PsiElement(Py:IDENTIFIER)('x')
PsiElement(Py:COMMA)(',')
PsiWhiteSpace(' ')
PyReferenceExpression: y
PsiElement(Py:IDENTIFIER)('y')
PsiElement(Py:RPAR)(')')
PsiWhiteSpace(' ')
PsiComment(Py:END_OF_LINE_COMMENT)('# loc will be x, not (x, y)')
PsiWhiteSpace('\n\n')
PyExpressionStatement
PyParenthesizedExpression
PsiElement(Py:LPAR)('(')
PyTupleExpression
PyReferenceExpression: px
PsiElement(Py:IDENTIFIER)('px')
PsiElement(Py:COMMA)(',')
PsiWhiteSpace(' ')
PyReferenceExpression: py
PsiElement(Py:IDENTIFIER)('py')
PsiElement(Py:COMMA)(',')
PsiWhiteSpace(' ')
PyAssignmentExpression
PyTargetExpression: pz
PsiElement(Py:IDENTIFIER)('pz')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyReferenceExpression: position
PsiElement(Py:IDENTIFIER)('position')
PsiElement(Py:RPAR)(')')
PsiWhiteSpace(' ')
PsiComment(Py:END_OF_LINE_COMMENT)('# pz will be position, px and py are references')
@@ -0,0 +1,10 @@
# Parenthesis everywhere because
# unparenthesized assignment expressions are prohibited
# at the top level of an expression statement
(x := y := z := 0)
(a[i] := x)
(self.rest := [])
(p: Optional[int] := None)
@@ -0,0 +1,81 @@
PyFile:InvalidAssignmentExpressions.py
PsiComment(Py:END_OF_LINE_COMMENT)('# Parenthesis everywhere because')
PsiWhiteSpace('\n')
PsiComment(Py:END_OF_LINE_COMMENT)('# unparenthesized assignment expressions are prohibited')
PsiWhiteSpace('\n')
PsiComment(Py:END_OF_LINE_COMMENT)('# at the top level of an expression statement')
PsiWhiteSpace('\n\n')
PyExpressionStatement
PyParenthesizedExpression
PsiElement(Py:LPAR)('(')
PyAssignmentExpression
PyTargetExpression: x
PsiElement(Py:IDENTIFIER)('x')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyReferenceExpression: y
PsiElement(Py:IDENTIFIER)('y')
PsiWhiteSpace(' ')
PsiErrorElement:Unexpected expression syntax
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PsiElement(Py:IDENTIFIER)('z')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PsiElement(Py:INTEGER_LITERAL)('0')
PsiElement(Py:RPAR)(')')
PsiWhiteSpace('\n\n')
PyExpressionStatement
PyParenthesizedExpression
PsiElement(Py:LPAR)('(')
PsiErrorElement:Identifier expected
PySubscriptionExpression
PyReferenceExpression: a
PsiElement(Py:IDENTIFIER)('a')
PsiElement(Py:LBRACKET)('[')
PyReferenceExpression: i
PsiElement(Py:IDENTIFIER)('i')
PsiElement(Py:RBRACKET)(']')
PsiWhiteSpace(' ')
PsiErrorElement:Unexpected expression syntax
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PsiElement(Py:IDENTIFIER)('x')
PsiElement(Py:RPAR)(')')
PsiWhiteSpace('\n')
PyExpressionStatement
PyParenthesizedExpression
PsiElement(Py:LPAR)('(')
PsiErrorElement:Identifier expected
PyReferenceExpression: rest
PyReferenceExpression: self
PsiElement(Py:IDENTIFIER)('self')
PsiElement(Py:DOT)('.')
PsiElement(Py:IDENTIFIER)('rest')
PsiWhiteSpace(' ')
PsiErrorElement:Unexpected expression syntax
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PsiElement(Py:LBRACKET)('[')
PsiElement(Py:RBRACKET)(']')
PsiElement(Py:RPAR)(')')
PsiWhiteSpace('\n\n')
PyExpressionStatement
PyParenthesizedExpression
PsiElement(Py:LPAR)('(')
PyReferenceExpression: p
PsiElement(Py:IDENTIFIER)('p')
PsiErrorElement:Unexpected expression syntax
PsiElement(Py:COLON)(':')
PsiWhiteSpace(' ')
PsiElement(Py:IDENTIFIER)('Optional')
PsiElement(Py:LBRACKET)('[')
PsiElement(Py:IDENTIFIER)('int')
PsiElement(Py:RBRACKET)(']')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PsiElement(Py:NONE_KEYWORD)('None')
PsiElement(Py:RPAR)(')')
@@ -426,6 +426,16 @@ public class PythonHighlightingTest extends PyTestCase {
doTest(LanguageLevel.PYTHON37, false, false);
}
// PY-33886
public void testInvalidAssignmentExpressions() {
doTest(LanguageLevel.PYTHON38, false, false);
}
// PY-33886
public void testUnsupportedAssignmentExpressions() {
doTest(LanguageLevel.PYTHON37, false, false);
}
@NotNull
private static EditorColorsScheme createTemporaryColorScheme() {
EditorColorsManager manager = EditorColorsManager.getInstance();
@@ -906,6 +906,16 @@ public class PythonParsingTest extends ParsingTestCase {
doTest(LanguageLevel.PYTHON37);
}
// PY-33886
public void testAssignmentExpressions() {
doTest(LanguageLevel.PYTHON38);
}
// PY-33886
public void testInvalidAssignmentExpressions() {
doTest(LanguageLevel.PYTHON38);
}
public void doTest() {
doTest(LanguageLevel.PYTHON26);
}
@@ -235,6 +235,11 @@ public class PyCompatibilityInspectionTest extends PyInspectionTestCase {
);
}
// PY-33886
public void testAssignmentExpressions() {
doTest(LanguageLevel.PYTHON38);
}
private void doTest(@NotNull LanguageLevel level) {
runWithLanguageLevel(level, this::doTest);
}