From 9d766c24a9ae6ebba750164b5d798dc47526ccfc Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Mon, 5 Mar 2018 20:58:50 +0300 Subject: [PATCH] Introduce PyTargetExpression.multiResolveAssignedValue (PY-27788) It allows to process targets initialized from `@overload`s. --- .../python/psi/PyTargetExpression.java | 25 ++++++ .../psi/impl/PyReferenceExpressionImpl.java | 40 +++++---- .../psi/impl/PyTargetExpressionImpl.java | 85 +++++++++---------- .../a.py | 6 ++ .../stub.pyi | 8 ++ .../PyTypeCheckerInspectionTest.java | 5 ++ 6 files changed, 105 insertions(+), 64 deletions(-) create mode 100644 python/testData/inspections/PyTypeCheckerInspection/OverloadedFunctionAssignedToTargetInStub/a.py create mode 100644 python/testData/inspections/PyTypeCheckerInspection/OverloadedFunctionAssignedToTargetInStub/stub.pyi 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 results = qualifierType.resolveMember(component, null, AccessDirection.READ, - resolveContext); - if (results == null || results.isEmpty()) { - return null; - } - resolved = results.get(0).getElement(); - } - return resolved; + List 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(); + } }