diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyTargetExpression.java b/python/psi-api/src/com/jetbrains/python/psi/PyTargetExpression.java
index 18307c88ba5b..4057487750b8 100644
--- a/python/psi-api/src/com/jetbrains/python/psi/PyTargetExpression.java
+++ b/python/psi-api/src/com/jetbrains/python/psi/PyTargetExpression.java
@@ -22,6 +22,9 @@ import com.jetbrains.python.psi.stubs.PyTargetExpressionStub;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.util.Collections;
+import java.util.List;
+
/**
* @author yole
*/
@@ -45,10 +48,32 @@ public interface PyTargetExpression extends PyQualifiedExpression, PsiNamedEleme
* Resolves the value that maps to this target expression in an enclosing assignment expression.
*
* This method does not access AST if underlying PSI is stub based and the context doesn't allow switching to AST.
+ *
+ * @param resolveContext resolve context
+ * @return the resolved assigned value or null.
+ * @deprecated Use {@link PyTargetExpression#multiResolveAssignedValue(PyResolveContext)} instead.
+ * This method will be removed in 2018.3.
*/
@Nullable
+ @Deprecated
PsiElement resolveAssignedValue(@NotNull PyResolveContext resolveContext);
+ /**
+ * Multi-resolves the value that maps to this target expression in an enclosing assignment expression.
+ *
+ * This method does not access AST if underlying PSI is stub based and the context doesn't allow switching to AST.
+ *
+ * @param resolveContext resolve context
+ * @return the resolved assigned values or an empty list.
+ * Note: the returned list does not contain null values.
+ * @apiNote This method will be marked as abstract in 2018.3.
+ */
+ @NotNull
+ default List multiResolveAssignedValue(@NotNull PyResolveContext resolveContext) {
+ final PsiElement element = resolveAssignedValue(resolveContext);
+ return element == null ? Collections.emptyList() : Collections.singletonList(element);
+ }
+
/**
* Returns the qualified name (if there is any) assigned to the expression.
*
diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java
index 25ddea667b0b..242e01c940cc 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java
@@ -172,30 +172,28 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
if (element instanceof PyTargetExpression) {
final PyTargetExpression target = (PyTargetExpression)element;
- final PsiElement assignedFrom;
- if (context.maySwitchToAST(target)) {
- assignedFrom = target.findAssignedValue();
- }
- else {
- assignedFrom = target.resolveAssignedValue(resolveContext);
- }
+ final List assignedFromElements = context.maySwitchToAST(target)
+ ? Collections.singletonList(target.findAssignedValue())
+ : target.multiResolveAssignedValue(resolveContext);
- if (assignedFrom instanceof PyReferenceExpression) {
- final PyReferenceExpression assignedReference = (PyReferenceExpression)assignedFrom;
+ for (PsiElement assignedFrom : assignedFromElements) {
+ if (assignedFrom instanceof PyReferenceExpression) {
+ final PyReferenceExpression assignedReference = (PyReferenceExpression)assignedFrom;
- if (!visited.add(assignedReference)) continue;
+ if (!visited.add(assignedReference)) continue;
- queue.add(MultiFollowQueueNode.create(node, assignedReference));
- }
- else if (assignedFrom != null) {
- result.add(
- new QualifiedRatedResolveResult(
- assignedFrom,
- node.myQualifiers,
- resolveResult instanceof RatedResolveResult ? ((RatedResolveResult)resolveResult).getRate() : 0,
- resolveResult instanceof ImplicitResolveResult
- )
- );
+ queue.add(MultiFollowQueueNode.create(node, assignedReference));
+ }
+ else if (assignedFrom != null) {
+ result.add(
+ new QualifiedRatedResolveResult(
+ assignedFrom,
+ node.myQualifiers,
+ resolveResult instanceof RatedResolveResult ? ((RatedResolveResult)resolveResult).getRate() : 0,
+ resolveResult instanceof ImplicitResolveResult
+ )
+ );
+ }
}
}
else if (element instanceof PyElement && resolveResult.isValidResult()) {
diff --git a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java
index 12cfb2f29be8..8e9dca09c7d4 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java
@@ -6,10 +6,7 @@ import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
-import com.intellij.psi.PsiComment;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiPolyVariantReference;
-import com.intellij.psi.PsiReference;
+import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.SearchScope;
@@ -22,6 +19,7 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PlatformIcons;
+import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
@@ -48,10 +46,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
+import java.util.*;
import static com.jetbrains.python.psi.PyUtil.as;
@@ -142,15 +137,15 @@ public class PyTargetExpressionImpl extends PyBaseElementImpl types = StreamEx
+ .of(multiResolveAssignedValue(resolveContext))
+ .select(PyTypedElement.class)
+ .map(context::getType)
+ .toList();
+
+ return PyUnionType.union(types);
}
type = getTypeFromComment(this);
if (type != null) {
@@ -513,43 +508,47 @@ public class PyTargetExpressionImpl extends PyBaseElementImpl multiResolveAssignedValue(@NotNull PyResolveContext resolveContext) {
final TypeEvalContext context = resolveContext.getTypeEvalContext();
+
if (context.maySwitchToAST(this)) {
final PyExpression value = findAssignedValue();
- if (value != null) {
- final List results = PyUtil.multiResolveTopPriority(value, resolveContext);
- return !results.isEmpty() ? results.get(0) : null;
- }
- return null;
+ return value != null
+ ? ContainerUtil.filter(PyUtil.multiResolveTopPriority(value, resolveContext), Objects::nonNull)
+ : Collections.emptyList();
}
else {
final QualifiedName qName = getAssignedQName();
- if (qName != null) {
+
+ if (qName != null && qName.getComponentCount() != 0) {
final ScopeOwner owner = ScopeUtil.getScopeOwner(this);
if (owner instanceof PyTypedElement) {
- final List components = qName.getComponents();
- if (!components.isEmpty()) {
- PsiElement resolved = owner;
- for (String component : components) {
- if (!(resolved instanceof PyTypedElement)) {
- return null;
- }
- final PyType qualifierType = context.getType((PyTypedElement)resolved);
- if (qualifierType == null) {
- return null;
- }
- final List extends RatedResolveResult> results = qualifierType.resolveMember(component, null, AccessDirection.READ,
- resolveContext);
- if (results == null || results.isEmpty()) {
- return null;
- }
- resolved = results.get(0).getElement();
- }
- return resolved;
+ List extends RatedResolveResult> resolved =
+ Collections.singletonList(new RatedResolveResult(RatedResolveResult.RATE_NORMAL, owner));
+
+ for (String component : qName.getComponents()) {
+ resolved = PyUtil.filterTopPriorityResults(
+ StreamEx
+ .of(resolved)
+ .map(ResolveResult::getElement)
+ .select(PyTypedElement.class)
+ .map(context::getType)
+ .nonNull()
+ .flatCollection(qualifier -> qualifier.resolveMember(component, null, AccessDirection.READ, resolveContext))
+ .toList()
+ );
}
+
+ return ContainerUtil.mapNotNull(resolved, ResolveResult::getElement);
}
}
- return null;
+
+ return Collections.emptyList();
}
}
diff --git a/python/testData/inspections/PyTypeCheckerInspection/OverloadedFunctionAssignedToTargetInStub/a.py b/python/testData/inspections/PyTypeCheckerInspection/OverloadedFunctionAssignedToTargetInStub/a.py
new file mode 100644
index 000000000000..da713c1aac7c
--- /dev/null
+++ b/python/testData/inspections/PyTypeCheckerInspection/OverloadedFunctionAssignedToTargetInStub/a.py
@@ -0,0 +1,6 @@
+from stub import good, bad
+good('foo')
+good(5)
+
+bad('foo')
+bad(15)
diff --git a/python/testData/inspections/PyTypeCheckerInspection/OverloadedFunctionAssignedToTargetInStub/stub.pyi b/python/testData/inspections/PyTypeCheckerInspection/OverloadedFunctionAssignedToTargetInStub/stub.pyi
new file mode 100644
index 000000000000..99fe1e04c77d
--- /dev/null
+++ b/python/testData/inspections/PyTypeCheckerInspection/OverloadedFunctionAssignedToTargetInStub/stub.pyi
@@ -0,0 +1,8 @@
+from typing import overload
+
+@overload
+def good(default: int) -> int: ...
+@overload
+def good(default: str) -> str: ...
+
+bad = good
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java
index 5c35e6380ea8..416a5e14be1b 100644
--- a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java
+++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java
@@ -525,4 +525,9 @@ public class PyTypeCheckerInspectionTest extends PyInspectionTestCase {
public void testGenericWithTypeVarBounds() {
runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest);
}
+
+ // PY-27788
+ public void testOverloadedFunctionAssignedToTargetInStub() {
+ doMultiFileTest();
+ }
}