From 65c20d7064ea85e10ba2a945417efa054ca08ec8 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Tue, 16 Feb 2016 14:01:42 +0300 Subject: [PATCH] PY-18521 Consider references inside function type comments to mark corresponding imports as used Additionally PyImportOptimizer visits PSI comments collecting information about unused imports, otherwise it doesn't take into account references inside any kind of type comments. --- .../imports/PyImportOptimizer.java | 5 ++-- .../PyUnresolvedReferencesInspection.java | 30 +++++++++++++------ ...unctionTypeCommentUsesImportsFromTyping.py | 7 +++++ ...rtsFromTypingUnusedInTypeComments.after.py | 10 +++++++ .../importsFromTypingUnusedInTypeComments.py | 10 +++++++ .../python/PyOptimizeImportsTest.java | 6 ++++ .../PyUnresolvedReferencesInspectionTest.java | 6 ++++ 7 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 python/testData/inspections/PyUnresolvedReferencesInspection/functionTypeCommentUsesImportsFromTyping.py create mode 100644 python/testData/optimizeImports/importsFromTypingUnusedInTypeComments.after.py create mode 100644 python/testData/optimizeImports/importsFromTypingUnusedInTypeComments.py diff --git a/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java b/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java index 169fe306bfaf..a4e9ca4abb24 100644 --- a/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java +++ b/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java @@ -19,6 +19,7 @@ import com.google.common.collect.Ordering; import com.intellij.codeInspection.LocalInspectionToolSession; import com.intellij.lang.ImportOptimizer; import com.intellij.openapi.util.Condition; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.codeInsight.imports.AddImportHelper.ImportPriority; @@ -49,8 +50,8 @@ public class PyImportOptimizer implements ImportOptimizer { Collections.emptyList()); file.accept(new PyRecursiveElementVisitor() { @Override - public void visitPyElement(PyElement node) { - super.visitPyElement(node); + public void visitElement(PsiElement node) { + super.visitElement(node); node.accept(visitor); } }); diff --git a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java index be33d6f0eeb9..b80cfa0ecdde 100644 --- a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java @@ -42,6 +42,7 @@ import com.jetbrains.python.PyCustomType; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; import com.jetbrains.python.codeInsight.PyCustomMember; +import com.jetbrains.python.codeInsight.PyFunctionTypeCommentReferenceContributor; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.codeInsight.imports.AutoImportHintAction; @@ -286,6 +287,13 @@ public class PyUnresolvedReferencesInspection extends PyInspection { if (comment instanceof PsiLanguageInjectionHost) { processInjection((PsiLanguageInjectionHost)comment); } + if (PyFunctionTypeCommentReferenceContributor.TYPE_COMMENT_PATTERN.accepts(comment)) { + for (PsiReference reference : comment.getReferences()) { + if (reference instanceof PsiPolyVariantReference) { + markTargetImportsAsUsed((PsiPolyVariantReference)reference); + } + } + } } @Override @@ -323,15 +331,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection { if (element instanceof PyReferenceOwner) { final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext); final PsiPolyVariantReference reference = ((PyReferenceOwner)element).getReference(resolveContext); - final ResolveResult[] resolveResults = reference.multiResolve(false); - for (ResolveResult resolveResult : resolveResults) { - if (resolveResult instanceof ImportedResolveResult) { - final PyImportedNameDefiner definer = ((ImportedResolveResult)resolveResult).getDefiner(); - if (definer != null) { - myUsedImports.add(definer); - } - } - } + markTargetImportsAsUsed(reference); } } }.visitElement(pair.getFirst()); @@ -339,6 +339,18 @@ public class PyUnresolvedReferencesInspection extends PyInspection { } } + private void markTargetImportsAsUsed(@NotNull PsiPolyVariantReference reference) { + final ResolveResult[] resolveResults = reference.multiResolve(false); + for (ResolveResult resolveResult : resolveResults) { + if (resolveResult instanceof ImportedResolveResult) { + final PyImportedNameDefiner definer = ((ImportedResolveResult)resolveResult).getDefiner(); + if (definer != null) { + myUsedImports.add(definer); + } + } + } + } + private void processReference(PyElement node, @Nullable PsiReference reference) { if (!isEnabled(node) || reference == null || reference.isSoft()) { return; diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/functionTypeCommentUsesImportsFromTyping.py b/python/testData/inspections/PyUnresolvedReferencesInspection/functionTypeCommentUsesImportsFromTyping.py new file mode 100644 index 000000000000..10b8ea2282ad --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/functionTypeCommentUsesImportsFromTyping.py @@ -0,0 +1,7 @@ +from typing import List, Optional + + +def f(x, y): + # type: (int, List[int]) -> str + y.append(x) + return 'foo' \ No newline at end of file diff --git a/python/testData/optimizeImports/importsFromTypingUnusedInTypeComments.after.py b/python/testData/optimizeImports/importsFromTypingUnusedInTypeComments.after.py new file mode 100644 index 000000000000..dd0e3bb71148 --- /dev/null +++ b/python/testData/optimizeImports/importsFromTypingUnusedInTypeComments.after.py @@ -0,0 +1,10 @@ +from typing import List, Set + + +def f(x, y): + # type: (int, List[int]) -> str + y.append(x) + return 'foo' + + +xs = {1, 2, 3} # type: Set[int] diff --git a/python/testData/optimizeImports/importsFromTypingUnusedInTypeComments.py b/python/testData/optimizeImports/importsFromTypingUnusedInTypeComments.py new file mode 100644 index 000000000000..507921bcc9c4 --- /dev/null +++ b/python/testData/optimizeImports/importsFromTypingUnusedInTypeComments.py @@ -0,0 +1,10 @@ +from typing import List, Optional, Set + + +def f(x, y): + # type: (int, List[int]) -> str + y.append(x) + return 'foo' + + +xs = {1, 2, 3} # type: Set[int] diff --git a/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java b/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java index 40fa5081fc2a..9346c602c600 100644 --- a/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java +++ b/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java @@ -81,6 +81,12 @@ public class PyOptimizeImportsTest extends PyTestCase { myFixture.checkResultByFile(testName + "/main.after.py"); } + // PY-18521 + public void testImportsFromTypingUnusedInTypeComments() { + myFixture.copyDirectoryToProject("../typing", ""); + doTest(); + } + private void doTest() { myFixture.configureByFile(getTestName(true) + ".py"); OptimizeImportsAction.actionPerformedImpl(DataManager.getInstance().getDataContext(myFixture.getEditor().getContentComponent())); diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java index 72ed58e8d3f6..19b1c24fb1bf 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java @@ -542,6 +542,12 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase { doTest(); } + // PY-18521 + public void testFunctionTypeCommentUsesImportsFromTyping() { + myFixture.copyDirectoryToProject("typing", ""); + doTest(); + } + @NotNull @Override protected Class getInspectionClass() {