mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Introduce PyTargetExpression.multiResolveAssignedValue (PY-27788)
It allows to process targets initialized from `@overload`s.
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
from stub import good, bad
|
||||
good('foo')
|
||||
good(5)
|
||||
|
||||
bad('foo')
|
||||
bad(15)
|
||||
+8
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user