From adb7ef06c4faa89c200254b38f293a412e1e6a21 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Tue, 12 Dec 2017 17:12:04 +0300 Subject: [PATCH] PY-21864 Avoid unstubbing while accessing ahead of time annotations Namely, don't use Scope to find an annotation if AST access is not allowed. Added tests on handling of top-level variable annotations and class attributes annotations in other files. Supposedly, we don't resolve to unqualified target expressions in other scopes when unstubbing is forbidden (ordinary local variables are not preserved in PSI stubs). --- .../typing/PyTypingTypeProvider.java | 28 ++++++++++---- .../other.py | 3 ++ .../other.py | 3 ++ .../other.py | 3 ++ .../other.py | 3 ++ .../com/jetbrains/python/PyTypingTest.java | 37 +++++++++++++++++-- 6 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 python/testData/types/ClassAttributeAnnotationAheadOfTimeInAnotherFile/other.py create mode 100644 python/testData/types/TopLevelVariableAnnotationAheadOfTimeInAnotherFileForTarget/other.py create mode 100644 python/testData/types/TopLevelVariableAnnotationAheadOfTimeInAnotherFileUnpackingTarget/other.py create mode 100644 python/testData/types/TopLevelVariableAnnotationAheadOfTimeInAnotherFileWithTarget/other.py diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index cdd1132c14ce..2027723c92f5 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -378,14 +378,26 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { } } else { - final Scope scope = ControlFlowCache.getScope(scopeOwner); - return StreamEx.of(scope.getNamedElements(name, false)) - .select(PyTargetExpression.class) - .map(x -> getTypeFromTargetExpressionAnnotation(x, context)) - .nonNull() - .map(Ref::get) - .findFirst() - .orElse(null); + StreamEx candidates = null; + if (context.maySwitchToAST(target)) { + final Scope scope = ControlFlowCache.getScope(scopeOwner); + candidates = StreamEx.of(scope.getNamedElements(name, false)).select(PyTargetExpression.class); + } + // Unqualified target expression in either class or module + else if (scopeOwner instanceof PyFile) { + candidates = StreamEx.of(((PyFile)scopeOwner).getTopLevelAttributes()).filter(t -> name.equals(t.getName())); + } + else if (scopeOwner instanceof PyClass) { + candidates = StreamEx.of(((PyClass)scopeOwner).getClassAttributes()).filter(t -> name.equals(t.getName())); + } + if (candidates != null) { + return candidates + .map(x -> getTypeFromTargetExpressionAnnotation(x, context)) + .nonNull() + .map(Ref::get) + .findFirst() + .orElse(null); + } } } return null; diff --git a/python/testData/types/ClassAttributeAnnotationAheadOfTimeInAnotherFile/other.py b/python/testData/types/ClassAttributeAnnotationAheadOfTimeInAnotherFile/other.py new file mode 100644 index 000000000000..f461ba75df02 --- /dev/null +++ b/python/testData/types/ClassAttributeAnnotationAheadOfTimeInAnotherFile/other.py @@ -0,0 +1,3 @@ +class C: + attr: int + attr, _ = None, None diff --git a/python/testData/types/TopLevelVariableAnnotationAheadOfTimeInAnotherFileForTarget/other.py b/python/testData/types/TopLevelVariableAnnotationAheadOfTimeInAnotherFileForTarget/other.py new file mode 100644 index 000000000000..fdfc302cd0d9 --- /dev/null +++ b/python/testData/types/TopLevelVariableAnnotationAheadOfTimeInAnotherFileForTarget/other.py @@ -0,0 +1,3 @@ +x: int +for x in foo(): + expr = x diff --git a/python/testData/types/TopLevelVariableAnnotationAheadOfTimeInAnotherFileUnpackingTarget/other.py b/python/testData/types/TopLevelVariableAnnotationAheadOfTimeInAnotherFileUnpackingTarget/other.py new file mode 100644 index 000000000000..0b97de1c2fea --- /dev/null +++ b/python/testData/types/TopLevelVariableAnnotationAheadOfTimeInAnotherFileUnpackingTarget/other.py @@ -0,0 +1,3 @@ +x: int +x, y = foo() +expr = x \ No newline at end of file diff --git a/python/testData/types/TopLevelVariableAnnotationAheadOfTimeInAnotherFileWithTarget/other.py b/python/testData/types/TopLevelVariableAnnotationAheadOfTimeInAnotherFileWithTarget/other.py new file mode 100644 index 000000000000..4e1e852d478c --- /dev/null +++ b/python/testData/types/TopLevelVariableAnnotationAheadOfTimeInAnotherFileWithTarget/other.py @@ -0,0 +1,3 @@ +x: int +with foo() as x: + expr = x diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index acc84fe4d304..2255f76b1c12 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -729,6 +729,13 @@ public class PyTypingTest extends PyTestCase { } // PY-21864 + public void testTopLevelVariableAnnotationAheadOfTimeInAnotherFileWithTarget() { + doMultiFileStubAwareTest("int", + "from other import x\n" + + "\n" + + "expr = x"); + } + public void testLocalVariableAnnotationAheadOfTimeForTarget() { doTest("int", "x: int\n" + @@ -736,6 +743,14 @@ public class PyTypingTest extends PyTestCase { " expr = x\n"); } + // PY-21864 + public void testTopLevelVariableAnnotationAheadOfTimeInAnotherFileForTarget() { + doMultiFileStubAwareTest("int", + "from other import x\n" + + "\n" + + "expr = x"); + } + // PY-21864 public void testLocalVariableAnnotationAheadOfTimeUnpackingTarget() { doTest("int", @@ -744,6 +759,14 @@ public class PyTypingTest extends PyTestCase { "expr = x"); } + // PY-21864 + public void testTopLevelVariableAnnotationAheadOfTimeInAnotherFileUnpackingTarget() { + doMultiFileStubAwareTest("int", + "from other import x\n" + + "\n" + + "expr = x"); + } + // PY-21864 public void testLocalVariableAnnotationAheadOfTimeOnlyFirstHintConsidered() { doTest("int", @@ -754,6 +777,14 @@ public class PyTypingTest extends PyTestCase { "expr = x"); } + // PY-21864 + public void testClassAttributeAnnotationAheadOfTimeInAnotherFile() { + doMultiFileStubAwareTest("int", + "from other import C\n" + + "\n" + + "expr = C().attr"); + } + public void testInstanceAttributeAnnotation() { doTest("int", "class C:\n" + @@ -1047,9 +1078,9 @@ public class PyTypingTest extends PyTestCase { // PY-24729 public void testAnnotatedInstanceAttributeInOtherFile() { doMultiFileStubAwareTest("int", - "from other import C\n" + - "\n" + - "expr = C().attr"); + "from other import C\n" + + "\n" + + "expr = C().attr"); } private void doTestNoInjectedText(@NotNull String text) {