PY-48940: Allow unparenthesized assignment expressions within sequence indexes, but not in slices.

(cherry picked from commit c8bfbc4149b840033f9c24a9190860abdd0ff6a8)

IJ-CR-11891

GitOrigin-RevId: 3ce6f98d5b031c705c461505a5c307c0241ce97e
This commit is contained in:
Irina Fediaeva
2021-07-16 19:10:46 +07:00
committed by intellij-monorepo-bot
parent a86c492a92
commit 333e20478b
6 changed files with 217 additions and 90 deletions

View File

@@ -525,41 +525,7 @@ public class ExpressionParsing extends Parsing {
}
else if (tokenType == PyTokenTypes.LBRACKET) {
myBuilder.advanceLexer();
SyntaxTreeBuilder.Marker sliceOrTupleStart = myBuilder.mark();
SyntaxTreeBuilder.Marker sliceItemStart = myBuilder.mark();
if (atToken(PyTokenTypes.COLON)) {
sliceOrTupleStart.drop();
SyntaxTreeBuilder.Marker sliceMarker = myBuilder.mark();
sliceMarker.done(PyElementTypes.EMPTY_EXPRESSION);
parseSliceEnd(expr, sliceItemStart);
}
else {
boolean hadExpression = parseSingleExpression(false);
if (atToken(PyTokenTypes.COLON)) {
sliceOrTupleStart.drop();
parseSliceEnd(expr, sliceItemStart);
}
else if (atToken(PyTokenTypes.COMMA)) {
sliceItemStart.done(PyElementTypes.SLICE_ITEM);
if (!parseSliceListTail(expr, sliceOrTupleStart)) {
sliceOrTupleStart.rollbackTo();
if (!parseTupleExpression(false, false, false)) {
myBuilder.error(message("tuple.expression.expected"));
}
checkMatches(PyTokenTypes.RBRACKET, message("PARSE.expected.rbracket"));
expr.done(PyElementTypes.SUBSCRIPTION_EXPRESSION);
}
}
else {
if (!hadExpression) {
myBuilder.error(message("PARSE.expected.expression"));
}
sliceOrTupleStart.drop();
sliceItemStart.drop();
checkMatches(PyTokenTypes.RBRACKET, message("PARSE.expected.rbracket"));
expr.done(PyElementTypes.SUBSCRIPTION_EXPRESSION);
}
}
parseSliceOrSubscriptionExpression(expr, false);
if (isTargetExpression && !recastQualifier) {
recastFirstIdentifier = true; // subscription is always a reference
recastQualifier = true; // recast non-first qualifiers too
@@ -581,6 +547,49 @@ public class ExpressionParsing extends Parsing {
return true;
}
private void parseSliceOrSubscriptionExpression(@NotNull SyntaxTreeBuilder.Marker expr, boolean isSlice) {
SyntaxTreeBuilder.Marker sliceOrTupleStart = myBuilder.mark();
SyntaxTreeBuilder.Marker sliceItemStart = myBuilder.mark();
if (atToken(PyTokenTypes.COLON)) {
sliceOrTupleStart.drop();
SyntaxTreeBuilder.Marker sliceMarker = myBuilder.mark();
sliceMarker.done(PyElementTypes.EMPTY_EXPRESSION);
parseSliceEnd(expr, sliceItemStart);
}
else {
var hadExpression = isSlice ? parseSingleExpression(false) : parseNamedTestExpression(false, false);
if (atToken(PyTokenTypes.COLON)) {
if (!isSlice) {
sliceOrTupleStart.rollbackTo();
parseSliceOrSubscriptionExpression(expr, true);
return;
}
sliceOrTupleStart.drop();
parseSliceEnd(expr, sliceItemStart);
}
else if (atToken(PyTokenTypes.COMMA)) {
sliceItemStart.done(PyElementTypes.SLICE_ITEM);
if (!parseSliceListTail(expr, sliceOrTupleStart)) {
sliceOrTupleStart.rollbackTo();
if (!parseTupleExpression(false, false, false)) {
myBuilder.error(message("tuple.expression.expected"));
}
checkMatches(PyTokenTypes.RBRACKET, message("PARSE.expected.rbracket"));
expr.done(PyElementTypes.SUBSCRIPTION_EXPRESSION);
}
}
else {
if (!hadExpression) {
myBuilder.error(message("PARSE.expected.expression"));
}
sliceOrTupleStart.drop();
sliceItemStart.drop();
checkMatches(PyTokenTypes.RBRACKET, message("PARSE.expected.rbracket"));
expr.done(PyElementTypes.SUBSCRIPTION_EXPRESSION);
}
}
}
private boolean parseEllipsis() {
if (atToken(PyTokenTypes.DOT)) {
final SyntaxTreeBuilder.Marker maybeEllipsis = myBuilder.mark();

View File

@@ -0,0 +1,8 @@
s = [1, 2]
s[(c := 0)] # valid
s[d := 0] # valid
s[(d := 0): (e := 1)] # valid
s[d := 0: (e := 1)] # invalid
s[d := 0: e := 1] # invalid

View File

@@ -0,0 +1,160 @@
PyFile:AssignmentExpressionsInIndexes.py
PyAssignmentStatement
PyTargetExpression: s
PsiElement(Py:IDENTIFIER)('s')
PsiWhiteSpace(' ')
PsiElement(Py:EQ)('=')
PsiWhiteSpace(' ')
PyListLiteralExpression
PsiElement(Py:LBRACKET)('[')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('1')
PsiElement(Py:COMMA)(',')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('2')
PsiElement(Py:RBRACKET)(']')
PsiWhiteSpace('\n\n')
PyExpressionStatement
PySubscriptionExpression
PyReferenceExpression: s
PsiElement(Py:IDENTIFIER)('s')
PsiElement(Py:LBRACKET)('[')
PyParenthesizedExpression
PsiElement(Py:LPAR)('(')
PyAssignmentExpression
PyTargetExpression: c
PsiElement(Py:IDENTIFIER)('c')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('0')
PsiElement(Py:RPAR)(')')
PsiElement(Py:RBRACKET)(']')
PsiWhiteSpace(' ')
PsiComment(Py:END_OF_LINE_COMMENT)('# valid')
PsiWhiteSpace('\n')
PyExpressionStatement
PySubscriptionExpression
PyReferenceExpression: s
PsiElement(Py:IDENTIFIER)('s')
PsiElement(Py:LBRACKET)('[')
PyAssignmentExpression
PyTargetExpression: d
PsiElement(Py:IDENTIFIER)('d')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('0')
PsiElement(Py:RBRACKET)(']')
PsiWhiteSpace(' ')
PsiComment(Py:END_OF_LINE_COMMENT)('# valid')
PsiWhiteSpace('\n\n')
PyExpressionStatement
PySliceExpression
PyReferenceExpression: s
PsiElement(Py:IDENTIFIER)('s')
PsiElement(Py:LBRACKET)('[')
PySliceItem
PyParenthesizedExpression
PsiElement(Py:LPAR)('(')
PyAssignmentExpression
PyTargetExpression: d
PsiElement(Py:IDENTIFIER)('d')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('0')
PsiElement(Py:RPAR)(')')
PsiElement(Py:COLON)(':')
PsiWhiteSpace(' ')
PyParenthesizedExpression
PsiElement(Py:LPAR)('(')
PyAssignmentExpression
PyTargetExpression: e
PsiElement(Py:IDENTIFIER)('e')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('1')
PsiElement(Py:RPAR)(')')
PsiElement(Py:RBRACKET)(']')
PsiWhiteSpace(' ')
PsiComment(Py:END_OF_LINE_COMMENT)('# valid')
PsiWhiteSpace('\n')
PyAssignmentExpression
PySubscriptionExpression
PyReferenceExpression: s
PsiElement(Py:IDENTIFIER)('s')
PsiElement(Py:LBRACKET)('[')
PyReferenceExpression: d
PsiElement(Py:IDENTIFIER)('d')
PsiErrorElement:']' expected
<empty list>
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('0')
PsiElement(Py:COLON)(':')
PsiErrorElement:Statement expected, found Py:IDENTIFIER
<empty list>
PsiWhiteSpace(' ')
PyExpressionStatement
PyParenthesizedExpression
PsiElement(Py:LPAR)('(')
PyAssignmentExpression
PyTargetExpression: e
PsiElement(Py:IDENTIFIER)('e')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('1')
PsiElement(Py:RPAR)(')')
PsiErrorElement:End of statement expected
<empty list>
PsiElement(Py:RBRACKET)(']')
PsiErrorElement:Statement expected, found Py:RBRACKET
<empty list>
PsiWhiteSpace(' ')
PsiComment(Py:END_OF_LINE_COMMENT)('# invalid')
PsiWhiteSpace('\n')
PyAssignmentExpression
PySubscriptionExpression
PyReferenceExpression: s
PsiElement(Py:IDENTIFIER)('s')
PsiElement(Py:LBRACKET)('[')
PyReferenceExpression: d
PsiElement(Py:IDENTIFIER)('d')
PsiErrorElement:']' expected
<empty list>
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('0')
PsiElement(Py:COLON)(':')
PsiErrorElement:Statement expected, found Py:IDENTIFIER
<empty list>
PsiWhiteSpace(' ')
PyExpressionStatement
PyAssignmentExpression
PyTargetExpression: e
PsiElement(Py:IDENTIFIER)('e')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('1')
PsiErrorElement:End of statement expected
<empty list>
PsiElement(Py:RBRACKET)(']')
PsiErrorElement:Statement expected, found Py:RBRACKET
<empty list>
PsiWhiteSpace(' ')
PsiComment(Py:END_OF_LINE_COMMENT)('# invalid')

View File

@@ -21,10 +21,6 @@ result_dict = {(a := 1) : (b := 2)}
assert a := 1 # INVALID
assert (a := 1)
l = [1, 2]
l[a := 0] # INVALID
l[(a := 0)]
with f := open('file.txt'): # INVALID
pass

View File

@@ -415,57 +415,6 @@ PyFile:InvalidNonParenthesizedAssignmentExpressions.py
PsiElement(Py:INTEGER_LITERAL)('1')
PsiElement(Py:RPAR)(')')
PsiWhiteSpace('\n\n')
PyAssignmentStatement
PyTargetExpression: l
PsiElement(Py:IDENTIFIER)('l')
PsiWhiteSpace(' ')
PsiElement(Py:EQ)('=')
PsiWhiteSpace(' ')
PyListLiteralExpression
PsiElement(Py:LBRACKET)('[')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('1')
PsiElement(Py:COMMA)(',')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('2')
PsiElement(Py:RBRACKET)(']')
PsiWhiteSpace('\n')
PyExpressionStatement
PySubscriptionExpression
PyReferenceExpression: l
PsiElement(Py:IDENTIFIER)('l')
PsiElement(Py:LBRACKET)('[')
PyAssignmentExpression
PyTargetExpression: a
PsiElement(Py:IDENTIFIER)('a')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('0')
PsiElement(Py:RBRACKET)(']')
PsiWhiteSpace(' ')
PsiComment(Py:END_OF_LINE_COMMENT)('# INVALID')
PsiWhiteSpace('\n')
PyExpressionStatement
PySubscriptionExpression
PyReferenceExpression: l
PsiElement(Py:IDENTIFIER)('l')
PsiElement(Py:LBRACKET)('[')
PyParenthesizedExpression
PsiElement(Py:LPAR)('(')
PyAssignmentExpression
PyTargetExpression: a
PsiElement(Py:IDENTIFIER)('a')
PsiWhiteSpace(' ')
PsiElement(Py:COLONEQ)(':=')
PsiWhiteSpace(' ')
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('0')
PsiElement(Py:RPAR)(')')
PsiElement(Py:RBRACKET)(']')
PsiWhiteSpace('\n\n')
PyWithStatement
PsiElement(Py:WITH_KEYWORD)('with')
PsiWhiteSpace(' ')

View File

@@ -1191,6 +1191,11 @@ public class PythonParsingTest extends ParsingTestCase {
doTest(LanguageLevel.getLatest());
}
// PY-48940
public void testAssignmentExpressionsInIndexes() {
doTest(LanguageLevel.getLatest());
}
public void doTest() {
doTest(LanguageLevel.PYTHON26);
}