diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 99dc8017f979..f327a1b0e2cb 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -227,6 +227,7 @@ INSP.module.$0.not.found=Module ''{0}'' not found INSP.unresolved.ref.$0=Unresolved reference ''{0}'' INSP.unresolved.ref.$0.for.class.$1=Unresolved attribute reference ''{0}'' for class ''{1}'' INSP.cannot.find.$0.in.$1=Cannot find reference ''{0}'' in ''{1}'' +INSP.try.except.import.error=''{0}'' in try block with ''except ImportError'' should also be defined in except block # ReturnValueFromInitInspection INSP.NAME.init.return=__init__ method that returns a value diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java index 700d30719949..fd9d0ddc0dd7 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java @@ -170,6 +170,15 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { @Override public void visitPyImportStatement(final PyImportStatement node) { + visitPyImportStatementBase(node); + } + + @Override + public void visitPyFromImportStatement(PyFromImportStatement node) { + visitPyImportStatementBase(node); + } + + private void visitPyImportStatementBase(PyImportStatementBase node) { myBuilder.startNode(node); for (PyImportElement importElement : node.getImportElements()) { final PyReferenceExpression importReference = importElement.getImportReference(); diff --git a/python/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java b/python/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java index 52c5ced653fe..d73ffd1c6330 100644 --- a/python/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java +++ b/python/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java @@ -26,4 +26,10 @@ public interface Scope { boolean isNonlocal(String name); boolean containsDeclaration(String name); + + /** + * @return all declarations available at the end of the scope + */ + @NotNull + Collection getAllDeclaredVariables(); } diff --git a/python/src/com/jetbrains/python/codeInsight/dataflow/scope/ScopeUtil.java b/python/src/com/jetbrains/python/codeInsight/dataflow/scope/ScopeUtil.java index e24e26929664..15484e8ce39c 100644 --- a/python/src/com/jetbrains/python/codeInsight/dataflow/scope/ScopeUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/dataflow/scope/ScopeUtil.java @@ -68,4 +68,20 @@ public class ScopeUtil { } return null; } + + public static boolean isDeclaredAndBoundInScope(PyElement element) { + final String name = element.getName(); + if (name != null) { + final ScopeOwner owner = getScopeOwner(element); + if (owner != null) { + final Scope scope = ControlFlowCache.getScope(owner); + for (ScopeVariable v : scope.getAllDeclaredVariables()) { + if (v.getName().equals(name)) { + return true; + } + } + } + } + return false; + } } diff --git a/python/src/com/jetbrains/python/inspections/PyUnboundLocalVariableInspection.java b/python/src/com/jetbrains/python/inspections/PyUnboundLocalVariableInspection.java index d242d96a324c..7915c3f51727 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnboundLocalVariableInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyUnboundLocalVariableInspection.java @@ -91,7 +91,7 @@ public class PyUnboundLocalVariableInspection extends PyInspection { continue; } } - if (PyAssignmentStatementNavigator.getStatementByTarget(element) != null || + if (PyAssignmentStatementNavigator.getStatementByTarget(element) != null || PyForStatementNavigator.getPyForStatementByIterable(element) != null || PyExceptPartNavigator.getPyExceptPartByTarget(element) != null || PyListCompExpressionNavigator.getPyListCompExpressionByVariable(element) != null || diff --git a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java index 6a5f7a6d1189..467984a17d45 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java @@ -19,6 +19,7 @@ import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyNames; import com.jetbrains.python.actions.*; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; +import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.codeInsight.imports.AutoImportHintAction; import com.jetbrains.python.codeInsight.imports.AutoImportQuickFix; import com.jetbrains.python.codeInsight.imports.OptimizeImportsQuickFix; @@ -71,7 +72,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection { } @Override - public void inspectionFinished(LocalInspectionToolSession session) { + public void inspectionFinished(LocalInspectionToolSession session, ProblemsHolder holder) { final Visitor visitor = session.getUserData(KEY); assert visitor != null; if (PyCodeInsightSettings.getInstance().HIGHLIGHT_UNUSED_IMPORTS) { @@ -137,6 +138,25 @@ public class PyUnresolvedReferencesInspection extends PyInspection { myAllImports.add(node); } + private static boolean isGuardedByImportError(PyElement node) { + final PyImportStatementBase importStatement = PsiTreeUtil.getParentOfType(node, PyImportStatementBase.class); + if (importStatement != null) { + final PyTryPart tryPart = PsiTreeUtil.getParentOfType(node, PyTryPart.class); + if (tryPart != null) { + final PyTryExceptStatement tryExceptStatement = PsiTreeUtil.getParentOfType(tryPart, PyTryExceptStatement.class); + if (tryExceptStatement != null) { + for (PyExceptPart exceptPart : tryExceptStatement.getExceptParts()) { + final PyExpression expr = exceptPart.getExceptClass(); + if (expr != null && "ImportError".equals(expr.getName())) { + return true; + } + } + } + } + } + return false; + } + @Override public void visitPyElement(final PyElement node) { super.visitPyElement(node); @@ -147,6 +167,15 @@ public class PyUnresolvedReferencesInspection extends PyInspection { severity = ((PsiReferenceEx) reference).getUnresolvedHighlightSeverity(myTypeEvalContext); if (severity == null) continue; } + if (isGuardedByImportError(node)) { + if (PsiTreeUtil.getParentOfType(node, PyImportElement.class) != null) { + if (!ScopeUtil.isDeclaredAndBoundInScope(node)) { + registerProblem(node, PyBundle.message("INSP.try.except.import.error", node.getName()), + ProblemHighlightType.LIKE_UNKNOWN_SYMBOL, null); + } + } + return; + } boolean unresolved; if (reference instanceof PsiPolyVariantReference) { final PsiPolyVariantReference poly = (PsiPolyVariantReference)reference; @@ -178,7 +207,6 @@ public class PyUnresolvedReferencesInspection extends PyInspection { final PsiElement ref_element = reference.getElement(); final boolean ref_is_importable = PythonReferenceImporter.isImportable(ref_element); final List actions = new ArrayList(2); - HintAction hintAction = null; if (ref_text.length() <= 0) return; // empty text, nothing to highlight if (reference.getElement() instanceof PyReferenceExpression) { PyReferenceExpression refex = (PyReferenceExpression)reference.getElement(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyImportStatementNavigator.java b/python/src/com/jetbrains/python/psi/impl/PyImportStatementNavigator.java index 7e6222c417d1..e6a3fd01ba4a 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyImportStatementNavigator.java +++ b/python/src/com/jetbrains/python/psi/impl/PyImportStatementNavigator.java @@ -4,6 +4,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.psi.PyImportElement; import com.jetbrains.python.psi.PyImportStatement; +import com.jetbrains.python.psi.PyImportStatementBase; import org.jetbrains.annotations.Nullable; /** @@ -14,9 +15,9 @@ public class PyImportStatementNavigator { } @Nullable - public static PyImportStatement getImportStatementByElement(final PsiElement element){ - final PyImportStatement statement = PsiTreeUtil.getParentOfType(element, PyImportStatement.class, false); - if (statement == null){ + public static PyImportStatementBase getImportStatementByElement(final PsiElement element){ + final PyImportStatementBase statement = PsiTreeUtil.getParentOfType(element, PyImportStatementBase.class, false); + if (statement == null) { return null; } for (PyImportElement importElement : statement.getImportElements()) { diff --git a/python/testData/codeInsight/controlflow/doubletry.txt b/python/testData/codeInsight/controlflow/doubletry.txt index 4a98ba533cc8..9bb500bafedb 100644 --- a/python/testData/codeInsight/controlflow/doubletry.txt +++ b/python/testData/codeInsight/controlflow/doubletry.txt @@ -1,20 +1,19 @@ 0(1) element: null 1(2) element: PyTryExceptStatement 2(3) element: PyTryPart -3(4,6) element: PyFromImportStatement -4(5,6) READ ACCESS: mercurial -5(6,10) READ ACCESS: lsprof -6(7) element: PyExceptPart -7(8) READ ACCESS: ImportError -8(9) element: PyRaiseStatement -9(19) READ ACCESS: Error -10(11) element: PyAssignmentStatement -11(12) WRITE ACCESS: p -12(13) element: PyTryExceptStatement -13(14) element: PyTryPart -14(15) element: PyReturnStatement -15(16) READ ACCESS: foo -16(17) element: PyFinallyPart -17(18) element: PyPrintStatement -18(19) READ ACCESS: p -19() element: null \ No newline at end of file +3(4,5) element: PyFromImportStatement +4(5,9) WRITE ACCESS: lsprof +5(6) element: PyExceptPart +6(7) READ ACCESS: ImportError +7(8) element: PyRaiseStatement +8(18) READ ACCESS: Error +9(10) element: PyAssignmentStatement +10(11) WRITE ACCESS: p +11(12) element: PyTryExceptStatement +12(13) element: PyTryPart +13(14) element: PyReturnStatement +14(15) READ ACCESS: foo +15(16) element: PyFinallyPart +16(17) element: PyPrintStatement +17(18) READ ACCESS: p +18() element: null \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/importExceptImportError.py b/python/testData/inspections/PyUnresolvedReferencesInspection/importExceptImportError.py new file mode 100644 index 000000000000..b40e28fbeae7 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/importExceptImportError.py @@ -0,0 +1,26 @@ +# PY-3639 + +def f(x): + from foo import StringIO + return StringIO(x) + +def f(x): + try: + from foo import StringIO + except Exception: + pass + return x + +def f(x): + try: + from foo import StringIO + except ImportError: + pass + return StringIO(x) + +def f(x): + try: + from lib1 import StringIO + except ImportError: + StringIO = lambda x: x + return StringIO(x) diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java index f5a1036246a2..b73d93de0c88 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java @@ -30,6 +30,10 @@ public class PyUnresolvedReferencesInspectionTest extends PyLightFixtureTestCase doTest(); } + public void testImportExceptImportError() { + doTest(); + } + private void doTest() { myFixture.configureByFile("inspections/PyUnresolvedReferencesInspection/" + getTestName(true) + ".py"); myFixture.enableInspections(PyUnresolvedReferencesInspection.class);