PY-20744 Warn about illegal targets for variable annotations
authorMikhail Golubev <mikhail.golubev@jetbrains.com>
Wed, 14 Sep 2016 14:19:00 +0000 (17:19 +0300)
committerMikhail Golubev <mikhail.golubev@jetbrains.com>
Fri, 16 Sep 2016 05:16:26 +0000 (08:16 +0300)
python/src/com/jetbrains/python/validation/PyAnnotatingVisitor.java
python/src/com/jetbrains/python/validation/TypeAnnotationTargetAnnotator.java [new file with mode: 0644]
python/testData/highlighting/illegalVariableAnnotationTarget.py [new file with mode: 0644]
python/testSrc/com/jetbrains/python/PythonHighlightingTest.java

index d5d1964b0faab6a7e3474c1c3b6488527037da70..150bebc9a1c67009d306811e35dfccbeae28fed2 100644 (file)
@@ -33,6 +33,7 @@ public class PyAnnotatingVisitor implements Annotator {
   private static final Logger LOGGER = Logger.getInstance(PyAnnotatingVisitor.class.getName());
   private static final Class[] ANNOTATOR_CLASSES = new Class[] {
     AssignTargetAnnotator.class,
+    TypeAnnotationTargetAnnotator.class,
     ParameterListAnnotator.class,
     HighlightingAnnotator.class,
     ReturnAnnotator.class,
diff --git a/python/src/com/jetbrains/python/validation/TypeAnnotationTargetAnnotator.java b/python/src/com/jetbrains/python/validation/TypeAnnotationTargetAnnotator.java
new file mode 100644 (file)
index 0000000..d54de6f
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.validation;
+
+import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.impl.PyPsiUtils;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Mikhail Golubev
+ */
+public class TypeAnnotationTargetAnnotator extends PyAnnotator {
+  @Override
+  public void visitPyAssignmentStatement(PyAssignmentStatement node) {
+    if (node.getAnnotation() != null && LanguageLevel.forElement(node).isAtLeast(LanguageLevel.PYTHON36)) {
+      if (node.getRawTargets().length > 1) {
+        getHolder().createErrorAnnotation(node, "Variable annotation cannot be used in assignment with multiple targets");
+      }
+      final PyExpression target = node.getLeftHandSideExpression();
+      if (target != null) {
+        checkAnnotationTarget(target);
+      }
+    }
+  }
+
+  @Override
+  public void visitPyTypeDeclarationStatement(PyTypeDeclarationStatement node) {
+    if (node.getAnnotation() != null && LanguageLevel.forElement(node).isAtLeast(LanguageLevel.PYTHON36)) {
+      checkAnnotationTarget(node.getTarget());
+    }
+  }
+
+  private void checkAnnotationTarget(@NotNull PyExpression expression) {
+    final PyExpression innerExpr = PyPsiUtils.flattenParens(expression);
+    if (innerExpr instanceof PyTupleExpression || innerExpr instanceof PyListLiteralExpression) {
+      getHolder().createErrorAnnotation(innerExpr, "Variable annotation cannot be combined with tuple unpacking");
+    }
+    else if (innerExpr != null && !(innerExpr instanceof PyTargetExpression || innerExpr instanceof PySubscriptionExpression)) {
+      getHolder().createErrorAnnotation(innerExpr, "Illegal target for variable annotation");
+    }
+  }
+}
diff --git a/python/testData/highlighting/illegalVariableAnnotationTarget.py b/python/testData/highlighting/illegalVariableAnnotationTarget.py
new file mode 100644 (file)
index 0000000..cbaecf6
--- /dev/null
@@ -0,0 +1,12 @@
+(x): int = 42
+(((x))): float
+x['foo']: str
+f(42).attr: dict
+
+<error descr="Illegal target for variable annotation">2 ** 8</error>: int
+<error descr="Illegal target for variable annotation">f()</error>: bool
+<error descr="Variable annotation cannot be combined with tuple unpacking">x, y, z</error>: Tuple[int, ...]
+(<error descr="Variable annotation cannot be combined with tuple unpacking">x, y, z</error>): Tuple[int, int, int]
+<error descr="Variable annotation cannot be combined with tuple unpacking">[x, y, z]</error>: Tuple[Any, Any, Any]
+<error descr="Variable annotation cannot be combined with tuple unpacking">x, *xs</error>: tuple = range(10)
+<error descr="Variable annotation cannot be used in assignment with multiple targets">x:int = y = 42</error>
\ No newline at end of file
index b9c6daeaf54e5c206f8f14acbb6f70dc322ad99b..5991a6d16135b1b6bc08dde49040f96f1cc4f18d 100644 (file)
@@ -297,6 +297,10 @@ public class PythonHighlightingTest extends PyTestCase {
     doTest(LanguageLevel.PYTHON35, true, false);
   }
 
+  public void testIllegalVariableAnnotationTarget() {
+    doTest(LanguageLevel.PYTHON36, true, false);
+  }
+
   // ---
   private void doTest(final LanguageLevel languageLevel, final boolean checkWarnings, final boolean checkInfos) {
     PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), languageLevel);