mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-22971 Fixed: Support @typing.overload in regular Python files, not only in Python stubs
Don't take implementation into account while inferring call type if there is at least one overload.
This commit is contained in:
committed by
Semyon Proshev
parent
eaac813526
commit
4b7f436e13
@@ -22,6 +22,7 @@ import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.ResolveResult;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.text.CharArrayUtil;
|
||||
@@ -75,7 +76,8 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
|
||||
final List<PyCallExpression.PyRatedMarkedCallee> ratedMarkedCallees =
|
||||
PyUtil.filterTopPriorityResults(call.multiResolveRatedCallee(resolveContext));
|
||||
|
||||
final Object[] items = PyCallExpressionHelper.forEveryScopeTakeOverloadsOtherwiseImplementations(ratedMarkedCallees, typeEvalContext)
|
||||
final Object[] items = PyCallExpressionHelper
|
||||
.forEveryScopeTakeOverloadsOtherwiseImplementations(ratedMarkedCallees, ResolveResult::getElement, typeEvalContext)
|
||||
.map(ratedMarkedCallee -> Pair.createNonNull(call, ratedMarkedCallee.getMarkedCallee()))
|
||||
.toArray();
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ public class PyArgumentListInspection extends PyInspection {
|
||||
PyUtil.filterTopPriorityResults(call.multiResolveRatedCallee(resolveContext, implicitOffset));
|
||||
|
||||
return PyCallExpressionHelper
|
||||
.forEveryScopeTakeOverloadsOtherwiseImplementations(ratedMarkedCallees, context)
|
||||
.forEveryScopeTakeOverloadsOtherwiseImplementations(ratedMarkedCallees, ResolveResult::getElement, context)
|
||||
.map(ratedMarkedCallee -> PyCallExpressionHelper.mapArguments(call, ratedMarkedCallee.getMarkedCallee(), context))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ import com.intellij.codeInsight.completion.CompletionUtil;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiPolyVariantReference;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.ResolveResult;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
@@ -39,6 +39,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -394,17 +395,19 @@ public class PyCallExpressionHelper {
|
||||
}
|
||||
// normal cases
|
||||
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
|
||||
final PsiPolyVariantReference reference = ((PyReferenceExpression)callee).getReference(resolveContext);
|
||||
final List<PyType> members = new ArrayList<>();
|
||||
for (PsiElement target : PyUtil.multiResolveTopPriority(reference)) {
|
||||
PyUtil.verboseOnly(() ->PyPsiUtils.assertValid(target));
|
||||
if (target != null) {
|
||||
final Ref<? extends PyType> typeRef = getCallTargetReturnType(call, target, context);
|
||||
if (typeRef != null) {
|
||||
members.add(typeRef.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
final List<QualifiedRatedResolveResult> resolveResults =
|
||||
PyUtil.filterTopPriorityResults(multiResolveCallee(callee, resolveContext));
|
||||
|
||||
final List<PyType> members = StreamEx
|
||||
.of(forEveryScopeTakeOverloadsOtherwiseImplementations(resolveResults, ResolveResult::getElement, context))
|
||||
.map(ResolveResult::getElement)
|
||||
.nonNull()
|
||||
.peek(element -> PyUtil.verboseOnly(() -> PyPsiUtils.assertValid(element)))
|
||||
.map(element -> getCallTargetReturnType(call, element, context))
|
||||
.nonNull()
|
||||
.<PyType>map(Ref::get)
|
||||
.toList();
|
||||
|
||||
if (!members.isEmpty()) {
|
||||
return PyUnionType.union(members);
|
||||
}
|
||||
@@ -839,27 +842,29 @@ public class PyCallExpressionHelper {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Stream<PyCallExpression.PyRatedMarkedCallee> forEveryScopeTakeOverloadsOtherwiseImplementations(@NotNull List<PyCallExpression.PyRatedMarkedCallee> callees,
|
||||
@NotNull TypeEvalContext context) {
|
||||
if (!containsOverloadsAndImplementations(callees, context)) {
|
||||
return callees.stream();
|
||||
public static <E> Stream<E> forEveryScopeTakeOverloadsOtherwiseImplementations(@NotNull List<E> elements,
|
||||
@NotNull Function<? super E, PsiElement> mapper,
|
||||
@NotNull TypeEvalContext context) {
|
||||
if (!containsOverloadsAndImplementations(elements, mapper, context)) {
|
||||
return elements.stream();
|
||||
}
|
||||
|
||||
return StreamEx
|
||||
.of(callees)
|
||||
.groupingBy(callee -> ScopeUtil.getScopeOwner(callee.getElement()))
|
||||
.of(elements)
|
||||
.groupingBy(element -> ScopeUtil.getScopeOwner(mapper.apply(element)))
|
||||
.values()
|
||||
.stream()
|
||||
.flatMap(oneScopeCallees -> takeOverloadsOtherwiseImplementations(oneScopeCallees, context));
|
||||
.flatMap(oneScopeElements -> takeOverloadsOtherwiseImplementations(oneScopeElements, mapper, context));
|
||||
}
|
||||
|
||||
private static boolean containsOverloadsAndImplementations(@NotNull List<PyCallExpression.PyRatedMarkedCallee> callees,
|
||||
@NotNull TypeEvalContext context) {
|
||||
private static <E> boolean containsOverloadsAndImplementations(@NotNull List<E> elements,
|
||||
@NotNull Function<? super E, PsiElement> mapper,
|
||||
@NotNull TypeEvalContext context) {
|
||||
boolean containsOverloads = false;
|
||||
boolean containsImplementations = false;
|
||||
|
||||
for (PyCallExpression.PyRatedMarkedCallee callee : callees) {
|
||||
final boolean overload = PyiUtil.isOverload(callee.getElement(), context);
|
||||
for (E element : elements) {
|
||||
final boolean overload = PyiUtil.isOverload(mapper.apply(element), context);
|
||||
containsOverloads |= overload;
|
||||
containsImplementations |= !overload;
|
||||
|
||||
@@ -870,13 +875,14 @@ public class PyCallExpressionHelper {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Stream<PyCallExpression.PyRatedMarkedCallee> takeOverloadsOtherwiseImplementations(@NotNull List<PyCallExpression.PyRatedMarkedCallee> callees,
|
||||
@NotNull TypeEvalContext context) {
|
||||
if (!containsOverloadsAndImplementations(callees, context)) {
|
||||
return callees.stream();
|
||||
private static <E> Stream<E> takeOverloadsOtherwiseImplementations(@NotNull List<E> elements,
|
||||
@NotNull Function<? super E, PsiElement> mapper,
|
||||
@NotNull TypeEvalContext context) {
|
||||
if (!containsOverloadsAndImplementations(elements, mapper, context)) {
|
||||
return elements.stream();
|
||||
}
|
||||
|
||||
return callees.stream().filter(callee -> PyiUtil.isOverload(callee.getElement(), context));
|
||||
return elements.stream().filter(element -> PyiUtil.isOverload(mapper.apply(element), context));
|
||||
}
|
||||
|
||||
public static class ArgumentMappingResults {
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.jetbrains.python.psi.types;
|
||||
import com.intellij.openapi.extensions.Extensions;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiNamedElement;
|
||||
import com.intellij.psi.ResolveResult;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PyNames;
|
||||
@@ -627,7 +628,7 @@ public class PyTypeChecker {
|
||||
final List<PyCallExpression.PyRatedMarkedCallee> ratedMarkedCallees =
|
||||
PyUtil.filterTopPriorityResults(((PyCallExpression)callSite).multiResolveRatedCallee(resolveContext));
|
||||
|
||||
return forEveryScopeTakeOverloadsOtherwiseImplementations(ratedMarkedCallees, context)
|
||||
return forEveryScopeTakeOverloadsOtherwiseImplementations(ratedMarkedCallees, ResolveResult::getElement, context)
|
||||
.map(PyCallExpression.PyRatedMarkedCallee::getElement)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -91,9 +91,8 @@ public class PyiTypeProvider extends PyTypeProviderBase {
|
||||
if (pythonStub instanceof PyFunction) {
|
||||
return getOverloadedCallType((PyFunction)pythonStub, callSite, context);
|
||||
}
|
||||
else if (function.getContainingFile() instanceof PyiFile) {
|
||||
return getOverloadedCallType(function, callSite, context);
|
||||
}
|
||||
|
||||
return getOverloadedCallType(function, callSite, context);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
from typing import overload
|
||||
|
||||
|
||||
class A:
|
||||
@overload
|
||||
def foo(self, value: int) -> int:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def foo(self, value: str) -> str:
|
||||
pass
|
||||
|
||||
def foo(self, value):
|
||||
return None
|
||||
@@ -0,0 +1,15 @@
|
||||
from typing import overload
|
||||
|
||||
|
||||
@overload
|
||||
def foo(value: int) -> int:
|
||||
pass
|
||||
|
||||
|
||||
@overload
|
||||
def foo(value: str) -> str:
|
||||
pass
|
||||
|
||||
|
||||
def foo(value):
|
||||
return None
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
from typing import overload
|
||||
|
||||
|
||||
class A:
|
||||
@overload
|
||||
def foo(self, value: int) -> int:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def foo(self, value: str) -> str:
|
||||
pass
|
||||
|
||||
def foo(self, value):
|
||||
return None
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
from typing import overload
|
||||
|
||||
|
||||
@overload
|
||||
def foo(value: int) -> int:
|
||||
pass
|
||||
|
||||
|
||||
@overload
|
||||
def foo(value: str) -> str:
|
||||
pass
|
||||
|
||||
|
||||
def foo(value):
|
||||
return None
|
||||
@@ -0,0 +1,14 @@
|
||||
from typing import overload
|
||||
|
||||
|
||||
class A:
|
||||
@overload
|
||||
def foo(self, value: int) -> int:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def foo(self, value: str) -> str:
|
||||
pass
|
||||
|
||||
def foo(self, value):
|
||||
return None
|
||||
@@ -0,0 +1,15 @@
|
||||
from typing import overload
|
||||
|
||||
|
||||
@overload
|
||||
def foo(value: int) -> int:
|
||||
pass
|
||||
|
||||
|
||||
@overload
|
||||
def foo(value: str) -> str:
|
||||
pass
|
||||
|
||||
|
||||
def foo(value):
|
||||
return None
|
||||
@@ -1714,6 +1714,177 @@ public class PyTypeTest extends PyTestCase {
|
||||
getTypeEvalContexts(expression).forEach(context -> context.getType(expression));
|
||||
}
|
||||
|
||||
// PY-22971
|
||||
public void testFirstOverloadAndImplementationInClass() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON35,
|
||||
() -> doTest("int",
|
||||
"from typing import overload\n" +
|
||||
"class A:\n" +
|
||||
" @overload\n" +
|
||||
" def foo(self, value: int) -> int:\n" +
|
||||
" pass\n" +
|
||||
" @overload\n" +
|
||||
" def foo(self, value: str) -> str:\n" +
|
||||
" pass\n" +
|
||||
" def foo(self, value):\n" +
|
||||
" return None\n" +
|
||||
"expr = A().foo(5)")
|
||||
);
|
||||
}
|
||||
|
||||
// PY-22971
|
||||
public void testTopLevelFirstOverloadAndImplementation() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON35,
|
||||
() -> doTest("int",
|
||||
"from typing import overload\n" +
|
||||
"@overload\n" +
|
||||
"def foo(value: int) -> int:\n" +
|
||||
" pass\n" +
|
||||
"@overload\n" +
|
||||
"def foo(value: str) -> str:\n" +
|
||||
" pass\n" +
|
||||
"def foo(value):\n" +
|
||||
" return None\n" +
|
||||
"expr = foo(5)")
|
||||
);
|
||||
}
|
||||
|
||||
// PY-22971
|
||||
public void testFirstOverloadAndImplementationInImportedClass() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON35,
|
||||
() -> doMultiFileTest("int",
|
||||
"from b import A\n" +
|
||||
"expr = A().foo(5)")
|
||||
);
|
||||
}
|
||||
|
||||
// PY-22971
|
||||
public void testFirstOverloadAndImplementationInImportedModule() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON35,
|
||||
() -> doMultiFileTest("int",
|
||||
"from b import foo\n" +
|
||||
"expr = foo(5)")
|
||||
);
|
||||
}
|
||||
|
||||
// PY-22971
|
||||
public void testSecondOverloadAndImplementationInClass() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON35,
|
||||
() -> doTest("str",
|
||||
"from typing import overload\n" +
|
||||
"class A:\n" +
|
||||
" @overload\n" +
|
||||
" def foo(self, value: int) -> int:\n" +
|
||||
" pass\n" +
|
||||
" @overload\n" +
|
||||
" def foo(self, value: str) -> str:\n" +
|
||||
" pass\n" +
|
||||
" def foo(self, value):\n" +
|
||||
" return None\n" +
|
||||
"expr = A().foo(\"5\")")
|
||||
);
|
||||
}
|
||||
|
||||
// PY-22971
|
||||
public void testTopLevelSecondOverloadAndImplementation() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON35,
|
||||
() -> doTest("str",
|
||||
"from typing import overload\n" +
|
||||
"@overload\n" +
|
||||
"def foo(value: int) -> int:\n" +
|
||||
" pass\n" +
|
||||
"@overload\n" +
|
||||
"def foo(value: str) -> str:\n" +
|
||||
" pass\n" +
|
||||
"def foo(value):\n" +
|
||||
" return None\n" +
|
||||
"expr = foo(\"5\")")
|
||||
);
|
||||
}
|
||||
|
||||
// PY-22971
|
||||
public void testSecondOverloadAndImplementationInImportedClass() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON35,
|
||||
() -> doMultiFileTest("str",
|
||||
"from b import A\n" +
|
||||
"expr = A().foo(\"5\")")
|
||||
);
|
||||
}
|
||||
|
||||
// PY-22971
|
||||
public void testSecondOverloadAndImplementationInImportedModule() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON35,
|
||||
() -> doMultiFileTest("str",
|
||||
"from b import foo\n" +
|
||||
"expr = foo(\"5\")")
|
||||
);
|
||||
}
|
||||
|
||||
// PY-22971
|
||||
public void testNotMatchedOverloadsAndImplementationInClass() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON35,
|
||||
() -> doTest("Union[int, str]",
|
||||
"from typing import overload\n" +
|
||||
"class A:\n" +
|
||||
" @overload\n" +
|
||||
" def foo(self, value: int) -> int:\n" +
|
||||
" pass\n" +
|
||||
" @overload\n" +
|
||||
" def foo(self, value: str) -> str:\n" +
|
||||
" pass\n" +
|
||||
" def foo(self, value):\n" +
|
||||
" return None\n" +
|
||||
"expr = A().foo(object())")
|
||||
);
|
||||
}
|
||||
|
||||
// PY-22971
|
||||
public void testTopLevelNotMatchedOverloadsAndImplementation() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON35,
|
||||
() -> doTest("Union[int, str]",
|
||||
"from typing import overload\n" +
|
||||
"@overload\n" +
|
||||
"def foo(value: int) -> int:\n" +
|
||||
" pass\n" +
|
||||
"@overload\n" +
|
||||
"def foo(value: str) -> str:\n" +
|
||||
" pass\n" +
|
||||
"def foo(value):\n" +
|
||||
" return None\n" +
|
||||
"expr = foo(object())")
|
||||
);
|
||||
}
|
||||
|
||||
// PY-22971
|
||||
public void testNotMatchedOverloadsAndImplementationInImportedClass() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON35,
|
||||
() -> doMultiFileTest("Union[int, str]",
|
||||
"from b import A\n" +
|
||||
"expr = A().foo(object())")
|
||||
);
|
||||
}
|
||||
|
||||
// PY-22971
|
||||
public void testNotMatchedOverloadsAndImplementationInImportedModule() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON35,
|
||||
() -> doMultiFileTest("Union[int, str]",
|
||||
"from b import foo\n" +
|
||||
"expr = foo(object())")
|
||||
);
|
||||
}
|
||||
|
||||
private static List<TypeEvalContext> getTypeEvalContexts(@NotNull PyExpression element) {
|
||||
return ImmutableList.of(TypeEvalContext.codeAnalysis(element.getProject(), element.getContainingFile()).withTracing(),
|
||||
TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).withTracing());
|
||||
|
||||
Reference in New Issue
Block a user