diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/InstructionBuilder.java b/python/src/com/jetbrains/python/codeInsight/controlflow/InstructionBuilder.java index 724036f7d11d..0983aa0b1a25 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/InstructionBuilder.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/InstructionBuilder.java @@ -4,6 +4,7 @@ import com.google.common.collect.Lists; import com.intellij.codeInsight.controlflow.ControlFlowBuilder; import com.intellij.codeInsight.controlflow.Instruction; import com.jetbrains.python.psi.PyReferenceExpression; +import com.jetbrains.python.psi.impl.PyQualifiedName; import java.util.List; @@ -18,7 +19,9 @@ public class InstructionBuilder { List result = Lists.newArrayList(); for (PyTypeAssertionEvaluator.Assertion def: assertions) { final PyReferenceExpression e = def.getElement(); - result.add(ReadWriteInstruction.assertType(builder, e, e.getName(), def.getTypeEvalFunction())); + final PyQualifiedName qname = e.asQualifiedName(); + final String name = qname != null ? qname.toString() : e.getName(); + result.add(ReadWriteInstruction.assertType(builder, e, name, def.getTypeEvalFunction())); } return result; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java index 18614a0007f3..2df5a5830d2b 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java @@ -16,6 +16,7 @@ import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.PythonDialectsTokenSetProvider; import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; +import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.console.PydevConsoleRunner; import com.jetbrains.python.console.completion.PydevConsoleReference; import com.jetbrains.python.console.pydev.ConsoleCommunication; @@ -204,7 +205,9 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere } } ResolveResult[] targets = getReference(PyResolveContext.noImplicits().withTypeEvalContext(context)).multiResolve(false); - if (targets.length == 0) return null; + if (targets.length == 0) { + return getQualifiedReferenceTypeByControlFlow(context); + } for (ResolveResult resolveResult : targets) { PsiElement target = resolveResult.getElement(); if (target == this || target == null) { @@ -226,6 +229,24 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere } } + @Nullable + public PyType getQualifiedReferenceTypeByControlFlow(@NotNull TypeEvalContext context) { + PyExpression qualifier = getQualifier(); + if (context.allowDataFlow(this) && qualifier != null) { + PyExpression next = qualifier; + while (next != null) { + qualifier = next; + next = qualifier instanceof PyQualifiedExpression ? ((PyQualifiedExpression)qualifier).getQualifier() : null; + } + final ScopeOwner scopeOwner = ScopeUtil.getScopeOwner(this); + final PyQualifiedName qname = asQualifiedName(); + if (qname != null && scopeOwner != null) { + return getTypeByControlFlow(qname.toString(), context, qualifier, scopeOwner); + } + } + return null; + } + @Nullable public Ref getTypeOfProperty(@NotNull TypeEvalContext context) { final PyExpression qualifier = getQualifier(); @@ -309,23 +330,13 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere if ((target instanceof PyTargetExpression || target instanceof PyNamedParameter) && anchor != null && context.allowDataFlow(anchor)) { final ScopeOwner scopeOwner = PsiTreeUtil.getStubOrPsiParentOfType(anchor, ScopeOwner.class); if (scopeOwner != null && scopeOwner == PsiTreeUtil.getStubOrPsiParentOfType(target, ScopeOwner.class)) { - PyAugAssignmentStatement augAssignment = PsiTreeUtil.getParentOfType(anchor, PyAugAssignmentStatement.class); - try { - final List defs = PyDefUseUtil.getLatestDefs(scopeOwner, - ((PyElement)target).getName(), - augAssignment != null ? augAssignment : anchor, - true); - if (!defs.isEmpty()) { - PyType type = defs.get(0).getType(context, anchor); - for (int i = 1; i < defs.size(); i++) { - type = PyUnionType.union(type, defs.get(i).getType(context, anchor)); - } + final String name = ((PyElement)target).getName(); + if (name != null) { + final PyType type = getTypeByControlFlow(name, context, anchor, scopeOwner); + if (type != null) { return type; } } - catch (PyDefUseUtil.InstructionNotFoundException e) { - // ignore - } } } if (target instanceof PyFunction) { @@ -356,6 +367,27 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere return null; } + private static PyType getTypeByControlFlow(@NotNull String name, + @NotNull TypeEvalContext context, + @NotNull PyExpression anchor, + @NotNull ScopeOwner scopeOwner) { + PyAugAssignmentStatement augAssignment = PsiTreeUtil.getParentOfType(anchor, PyAugAssignmentStatement.class); + try { + final PyElement element = augAssignment != null ? augAssignment : anchor; + final List defs = PyDefUseUtil.getLatestDefs(scopeOwner, name, element, true); + if (!defs.isEmpty()) { + PyType type = defs.get(0).getType(context, anchor); + for (int i = 1; i < defs.size(); i++) { + type = PyUnionType.union(type, defs.get(i).getType(context, anchor)); + } + return type; + } + } + catch (PyDefUseUtil.InstructionNotFoundException ignored) { + } + return null; + } + @Nullable public static PyType getReferenceTypeFromProviders(@NotNull final PsiElement target, TypeEvalContext context, diff --git a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java index 9da2e5cded3b..6ff0197e7662 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java @@ -160,6 +160,10 @@ public class PyTargetExpressionImpl extends PyPresentableElementImpl elements = Collections.singletonList(ResolveImportUtil.resolveChild(resolved, name, null, true, true)); diff --git a/python/src/com/jetbrains/python/psi/types/PyUnionType.java b/python/src/com/jetbrains/python/psi/types/PyUnionType.java index ca774a21f61a..01d5ab3e7b86 100644 --- a/python/src/com/jetbrains/python/psi/types/PyUnionType.java +++ b/python/src/com/jetbrains/python/psi/types/PyUnionType.java @@ -32,7 +32,7 @@ public class PyUnionType implements PyType { boolean all_nulls = true; for (PyType member : myMembers) { if (member != null) { - List result = member.resolveMember(name, null, direction, resolveContext, true); + List result = member.resolveMember(name, null, direction, resolveContext, inherited); if (result != null) { all_nulls = false; ret.addAll(result); diff --git a/python/src/com/jetbrains/python/refactoring/PyDefUseUtil.java b/python/src/com/jetbrains/python/refactoring/PyDefUseUtil.java index 28e7b59c3b9e..3c9bff0960e5 100644 --- a/python/src/com/jetbrains/python/refactoring/PyDefUseUtil.java +++ b/python/src/com/jetbrains/python/refactoring/PyDefUseUtil.java @@ -12,6 +12,7 @@ import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyAugAssignmentStatementNavigator; +import com.jetbrains.python.psi.impl.PyQualifiedName; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -75,6 +76,12 @@ public class PyDefUseUtil { if (element instanceof PyImportElement) { return ((PyImportElement) element).getVisibleName(); } + if (element instanceof PyReferenceExpression) { + final PyQualifiedName qname = ((PyReferenceExpression)element).asQualifiedName(); + if (qname != null) { + return qname.toString(); + } + } return element instanceof PyElement ? ((PyElement)element).getName() : null; } diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 7c32aff8999c..0a7527488b28 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -497,6 +497,45 @@ public class PyTypeTest extends PyTestCase { " expr = x\n"); } + // PY-5614 + public void testUnknownReferenceTypeAttribute() { + doTest("str", + "def f(x):\n" + + " if isinstance(x.foo, str):\n" + + " expr = x.foo\n"); + } + + // PY-5614 + public void testUnknownTypeAttribute() { + doTest("str", + "class C(object):\n" + + " def __init__(self, foo):\n" + + " self.foo = foo\n" + + " def f(self):\n" + + " if isinstance(self.foo, str):\n" + + " expr = self.foo\n"); + } + + // PY-5614 + public void testKnownTypeAttribute() { + doTest("str", + "class C(object):\n" + + " def __init__(self):\n" + + " self.foo = 42\n" + + " def f(self):\n" + + " if isinstance(self.foo, str):\n" + + " expr = self.foo\n"); + } + + // PY-5614 + public void testNestedUnknownReferenceTypeAttribute() { + doTest("str", + "def f(x):\n" + + " if isinstance(x.foo.bar, str):\n" + + " expr = x.foo.bar\n"); + + } + private PyExpression parseExpr(String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); return myFixture.findElementByText("expr", PyExpression.class);