mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
committed by
intellij-monorepo-bot
parent
fccb1b8b79
commit
ccc2156cf9
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user