From 57a4d381e30ae93b13031c19b5ed7d7d556f2f17 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 14 Sep 2015 14:45:40 +0300 Subject: [PATCH] Updated star expression annotator to allow * unpacking in tuples and lists --- .../python/parsing/ExpressionParsing.java | 24 +++++++++++++++---- .../python/validation/StarAnnotator.java | 21 ++++++++++++---- .../testData/highlighting/starInGenerator.py | 2 +- python/testData/highlighting/unpackingStar.py | 9 +++++++ .../python/PythonHighlightingTest.java | 4 ++++ 5 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 python/testData/highlighting/unpackingStar.py diff --git a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java index d14a70ad03d3..ccb3b3e14a0e 100644 --- a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java +++ b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java @@ -190,7 +190,7 @@ public class ExpressionParsing extends Parsing { } if (atToken(PyTokenTypes.EXP)) { - if (!parseStarExpression(false)) { + if (!parseDoubleStarExpression(false)) { myBuilder.error("expression expected"); expr.done(PyElementTypes.DICT_LITERAL_EXPRESSION); return; @@ -248,7 +248,7 @@ public class ExpressionParsing extends Parsing { while (myBuilder.getTokenType() != PyTokenTypes.RBRACE) { checkMatches(PyTokenTypes.COMMA, message("PARSE.expected.comma")); if (atToken(PyTokenTypes.EXP)) { - if (!parseStarExpression(false)) { + if (!parseDoubleStarExpression(false)) { break; } } @@ -789,8 +789,7 @@ public class ExpressionParsing extends Parsing { } private boolean parseStarExpression(boolean isTargetExpression) { - final IElementType tokenType = myBuilder.getTokenType(); - if (tokenType == PyTokenTypes.MULT || tokenType == PyTokenTypes.EXP) { + if (atToken(PyTokenTypes.MULT)) { PsiBuilder.Marker starExpr = myBuilder.mark(); nextToken(); if (!parseBitwiseORExpression(isTargetExpression)) { @@ -798,7 +797,22 @@ public class ExpressionParsing extends Parsing { starExpr.drop(); return false; } - starExpr.done(tokenType == PyTokenTypes.MULT ? PyElementTypes.STAR_EXPRESSION : PyElementTypes.DOUBLE_STAR_EXPRESSION); + starExpr.done(PyElementTypes.STAR_EXPRESSION); + return true; + } + return parseBitwiseORExpression(isTargetExpression); + } + + private boolean parseDoubleStarExpression(boolean isTargetExpression) { + if (atToken(PyTokenTypes.EXP)) { + PsiBuilder.Marker starExpr = myBuilder.mark(); + nextToken(); + if (!parseBitwiseORExpression(isTargetExpression)) { + myBuilder.error(message("PARSE.expected.expression")); + starExpr.drop(); + return false; + } + starExpr.done(PyElementTypes.DOUBLE_STAR_EXPRESSION); return true; } return parseBitwiseORExpression(isTargetExpression); diff --git a/python/src/com/jetbrains/python/validation/StarAnnotator.java b/python/src/com/jetbrains/python/validation/StarAnnotator.java index 6daaec2e9a4d..0b6dd5372737 100644 --- a/python/src/com/jetbrains/python/validation/StarAnnotator.java +++ b/python/src/com/jetbrains/python/validation/StarAnnotator.java @@ -15,8 +15,9 @@ */ package com.jetbrains.python.validation; -import com.jetbrains.python.psi.PyStarExpression; -import com.jetbrains.python.psi.PyTargetExpression; +import com.intellij.psi.PsiElement; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.NotNull; /** * @author yole @@ -25,8 +26,20 @@ public class StarAnnotator extends PyAnnotator { @Override public void visitPyStarExpression(PyStarExpression node) { super.visitPyStarExpression(node); - if (!(node.getExpression() instanceof PyTargetExpression)) { - getHolder().createErrorAnnotation(node, "can use starred expression only as assignment target"); + if (!isAssignmentTarget(node) && !isStarUnpacking(node)) { + getHolder().createErrorAnnotation(node, "Can't use starred expression here"); } } + + private static boolean isStarUnpacking(@NotNull PyStarExpression node) { + PsiElement parent = node.getParent(); + while (parent instanceof PyParenthesizedExpression) { + parent = parent.getParent(); + } + return parent instanceof PyTupleExpression || parent instanceof PyListLiteralExpression; + } + + private static boolean isAssignmentTarget(@NotNull PyStarExpression node) { + return node.getExpression() instanceof PyTargetExpression; + } } diff --git a/python/testData/highlighting/starInGenerator.py b/python/testData/highlighting/starInGenerator.py index a5f9979baaa9..708541eaf698 100644 --- a/python/testData/highlighting/starInGenerator.py +++ b/python/testData/highlighting/starInGenerator.py @@ -1 +1 @@ -y = (*() for _ in ()) \ No newline at end of file +y = (*() for _ in ()) \ No newline at end of file diff --git a/python/testData/highlighting/unpackingStar.py b/python/testData/highlighting/unpackingStar.py new file mode 100644 index 000000000000..04e59c5a5ddb --- /dev/null +++ b/python/testData/highlighting/unpackingStar.py @@ -0,0 +1,9 @@ +1, *x +(1, *x) +[1, *x] + +if *x: + pass + +1 + (*x) +1 + (*x,) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java b/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java index d0d4e894ea23..e6ce4d1e19b4 100644 --- a/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java +++ b/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java @@ -241,6 +241,10 @@ public class PythonHighlightingTest extends PyTestCase { doTest(LanguageLevel.PYTHON35, false, false); } + public void testUnpackingStar() { + doTest(LanguageLevel.PYTHON35, false, false); + } + // --- private void doTest(final LanguageLevel languageLevel, final boolean checkWarnings, final boolean checkInfos) { PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), languageLevel);