Introduce PyTargetExpression.multiResolveAssignedValue (PY-27788)

It allows to process targets initialized from `@overload`s.
This commit is contained in:
Semyon Proshev
2018-03-13 16:50:56 +03:00
parent d015020a80
commit 9d766c24a9
6 changed files with 105 additions and 64 deletions
@@ -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.
* <i>Note: the returned list does not contain null values.</i>
* @apiNote This method will be marked as abstract in 2018.3.
*/
@NotNull
default List<PsiElement> 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.
*
@@ -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<PsiElement> 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()) {
@@ -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<PyTargetExpression
return type;
}
if (!context.maySwitchToAST(this)) {
final PsiElement value = resolveAssignedValue(PyResolveContext.noImplicits().withTypeEvalContext(context));
if (value instanceof PyTypedElement) {
type = context.getType((PyTypedElement)value);
if (type instanceof PyNoneType) {
return null;
}
return type;
}
return null;
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
final List<PyType> 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<PyTargetExpression
@Nullable
@Override
public PsiElement resolveAssignedValue(@NotNull PyResolveContext resolveContext) {
return ContainerUtil.getFirstItem(multiResolveAssignedValue(resolveContext));
}
@NotNull
@Override
public List<PsiElement> multiResolveAssignedValue(@NotNull PyResolveContext resolveContext) {
final TypeEvalContext context = resolveContext.getTypeEvalContext();
if (context.maySwitchToAST(this)) {
final PyExpression value = findAssignedValue();
if (value != null) {
final List<PsiElement> 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<String> 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();
}
}
@@ -0,0 +1,6 @@
from stub import good, bad
good('foo')
good(5)
bad('foo')
bad(15)
@@ -0,0 +1,8 @@
from typing import overload
@overload
def good(default: int) -> int: ...
@overload
def good(default: str) -> str: ...
bad = good
@@ -525,4 +525,9 @@ public class PyTypeCheckerInspectionTest extends PyInspectionTestCase {
public void testGenericWithTypeVarBounds() {
runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest);
}
// PY-27788
public void testOverloadedFunctionAssignedToTargetInStub() {
doMultiFileTest();
}
}