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);