PY-22971 Fixed: Support @typing.overload in regular Python files, not only in Python stubs

Resolve:
* resolve to the first implementation in class, if there is no implementation, resolve to the first overload
* resolve to the last implementation in module, if there is no implementation, resolve to the last overload
This commit is contained in:
Semyon Proshev
2017-04-10 16:35:24 +03:00
committed by Semyon Proshev
parent 986cfc4921
commit 947930f6c9
22 changed files with 593 additions and 12 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,6 +54,8 @@ import com.jetbrains.python.psi.stubs.PyFileStub;
import com.jetbrains.python.psi.types.PyModuleType;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.pyi.PyiTypeProvider;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -180,7 +182,8 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
}
}
}
return resultList;
return containsOverloads(resultList, typeEvalContext) ? moveOverloadsBack(resultList, typeEvalContext) : resultList;
}
synchronized (myNameDefinerNegativeCache) {
@@ -192,6 +195,34 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
public long getModificationStamp() {
return myModificationStamp;
}
private boolean containsOverloads(@NotNull List<RatedResolveResult> resolveResults, @NotNull TypeEvalContext context) {
return StreamEx
.of(resolveResults)
.map(RatedResolveResult::getElement)
.anyMatch(element -> element instanceof PyCallable && PyiTypeProvider.isOverload((PyCallable)element, context));
}
@NotNull
private List<RatedResolveResult> moveOverloadsBack(@NotNull List<RatedResolveResult> resolveResults, @NotNull TypeEvalContext context) {
return StreamEx
.of(resolveResults)
.sorted(
(r1, r2) -> {
final PsiElement e1 = r1.getElement();
final PsiElement e2 = r2.getElement();
if (e1 instanceof PyCallable && e2 instanceof PyCallable) {
final boolean firstIsOverload = PyiTypeProvider.isOverload((PyCallable)e1, context);
final boolean secondIsOverload = PyiTypeProvider.isOverload((PyCallable)e2, context);
return Boolean.compare(firstIsOverload, secondIsOverload);
}
return 0;
}
)
.toList();
}
}
public PyFileImpl(FileViewProvider viewProvider) {
@@ -210,6 +241,7 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
return PythonFileType.INSTANCE;
}
@Override
public String toString() {
return "PyFile:" + getName();
}
@@ -444,6 +476,7 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
return cache;
}
@Override
@Nullable
public PsiElement getElementNamed(final String name) {
final List<RatedResolveResult> results = multiResolveName(name);
@@ -458,6 +491,7 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
return null;
}
@Override
@NotNull
public Iterable<PyElement> iterateNames() {
final List<PyElement> result = new ArrayList<>();

View File

@@ -278,7 +278,7 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference
.select(PyCallable.class)
.filter(callable -> PyiTypeProvider.isOverload(callable, typeEvalContext))
.map(callable -> new RatedResolveResult(getRate(callable, typeEvalContext), callable))
.append(latestDefs)
.prepend(latestDefs)
.toList();
}

View File

@@ -44,12 +44,15 @@ import com.jetbrains.python.psi.resolve.CompletionVariantsProcessor;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.PyResolveProcessor;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import com.jetbrains.python.pyi.PyiTypeProvider;
import com.jetbrains.python.toolbox.Maybe;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.jetbrains.python.psi.PyUtil.as;
import static com.jetbrains.python.psi.resolve.PyResolveImportUtil.fromFoothold;
@@ -118,7 +121,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
public PyClassLikeType toClass() {
return myIsDefinition ? this : new PyClassTypeImpl(myClass, true);
}
/**
* Wrap new instance to copy user data to it
*/
@@ -208,7 +211,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
}
}
final List<? extends RatedResolveResult> classMembers = resolveInner(myClass, myIsDefinition, name, location);
final List<? extends RatedResolveResult> classMembers = resolveInner(myClass, myIsDefinition, name, location, context);
if (PyNames.__CLASS__.equals(name)) {
return resolveDunderClass(context, classMembers);
@@ -244,7 +247,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
type = type.toInstance();
}
final List<? extends RatedResolveResult> superMembers =
resolveInner(((PyClassType)type).getPyClass(), myIsDefinition, name, location);
resolveInner(((PyClassType)type).getPyClass(), myIsDefinition, name, location, context);
if (!superMembers.isEmpty()) {
return superMembers;
}
@@ -549,24 +552,53 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
private static List<? extends RatedResolveResult> resolveInner(@NotNull PyClass cls,
boolean isDefinition,
@NotNull String name,
@Nullable PyExpression location) {
@Nullable PyExpression location,
@NotNull TypeEvalContext context) {
final PyResolveProcessor processor = new PyResolveProcessor(name);
final Collection<PsiElement> result;
final Stream<PsiElement> result;
if (!isDefinition && !cls.processInstanceLevelDeclarations(processor, location)) {
result = processor.getElements();
result = processor.getElements().stream();
}
else {
cls.processClassLevelDeclarations(processor);
result = processor.getElements();
final Collection<PsiElement> elements = processor.getElements();
result = containsOverloads(elements, context) ? moveOverloadsBack(elements, context) : elements.stream();
}
return ContainerUtil.map(result, element -> new RatedResolveResult(RatedResolveResult.RATE_NORMAL, element));
return StreamEx
.of(result)
.map(element -> new RatedResolveResult(RatedResolveResult.RATE_NORMAL, element))
.toList();
}
private static boolean containsOverloads(@NotNull Collection<PsiElement> elements, @NotNull TypeEvalContext context) {
return ContainerUtil.exists(elements,
element -> element instanceof PyCallable && PyiTypeProvider.isOverload((PyCallable)element, context));
}
@NotNull
private static Stream<PsiElement> moveOverloadsBack(@NotNull Collection<PsiElement> elements, @NotNull TypeEvalContext context) {
return elements
.stream()
.sorted(
(e1, e2) -> {
if (e1 instanceof PyCallable && e2 instanceof PyCallable) {
final boolean firstIsOverload = PyiTypeProvider.isOverload((PyCallable)e1, context);
final boolean secondIsOverload = PyiTypeProvider.isOverload((PyCallable)e2, context);
return Boolean.compare(firstIsOverload, secondIsOverload);
}
return 0;
}
);
}
private static Key<Set<PyClassType>> CTX_VISITED = Key.create("PyClassType.Visited");
public static Key<Boolean> CTX_SUPPRESS_PARENTHESES = Key.create("PyFunction.SuppressParentheses");
@Override
public Object[] getCompletionVariants(String prefix, PsiElement location, ProcessingContext context) {
Set<PyClassType> visited = context.get(CTX_VISITED);
if (visited == null) {
@@ -794,6 +826,8 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
return lookupString.startsWith("_") && !lookupString.startsWith("__");
}
@Override
@Nullable
public String getName() {
return getPyClass().getName();
}
@@ -842,6 +876,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
return (isValid() ? "" : "[INVALID] ") + "PyClassType: " + getClassQName();
}
@Override
public boolean isValid() {
return myClass.isValid();
}
@@ -881,6 +916,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
this.instance = instance;
}
@Override
public boolean value(final PsiElement target) {
return (instance != target);
}

View File

@@ -0,0 +1,22 @@
from typing import overload
class A:
@overload
def foo(self, value: None) -> None:
pass
@overload
def foo(self, value: int) -> str:
pass
@overload
def foo(self, value: str) -> str:
pass
def foo(self, value):
return None
A().foo("abc")
<ref>

View File

@@ -0,0 +1,5 @@
from OverloadsAndImplementationInImportedClassDep import A
A().foo("abc")
<ref>

View File

@@ -0,0 +1,18 @@
from typing import overload
class A:
@overload
def foo(self, value: None) -> None:
pass
@overload
def foo(self, value: int) -> str:
pass
@overload
def foo(self, value: str) -> str:
pass
def foo(self, value):
return None

View File

@@ -0,0 +1,5 @@
from OverloadsAndImplementationInImportedModuleDep import foo
foo("abc")
<ref>

View File

@@ -0,0 +1,20 @@
from typing import overload
@overload
def foo(value: None) -> None:
pass
@overload
def foo(value: int) -> str:
pass
@overload
def foo(value: str) -> str:
pass
def foo(value):
return None

View File

@@ -0,0 +1,25 @@
from typing import overload
class A:
@overload
def foo(self, value: None) -> None:
pass
def foo(self, value):
return None
@overload
def foo(self, value: int) -> str:
pass
def foo(self, value):
return None
@overload
def foo(self, value: str) -> str:
pass
A().foo("abc")
<ref>

View File

@@ -0,0 +1,5 @@
from OverloadsAndImplementationsInImportedClassDep import A
A().foo("abc")
<ref>

View File

@@ -0,0 +1,21 @@
from typing import overload
class A:
@overload
def foo(self, value: None) -> None:
pass
def foo(self, value):
return None
@overload
def foo(self, value: int) -> str:
pass
def foo(self, value):
return None
@overload
def foo(self, value: str) -> str:
pass

View File

@@ -0,0 +1,5 @@
from OverloadsAndImplementationsInImportedModuleDep import foo
foo("abc")
<ref>

View File

@@ -0,0 +1,24 @@
from typing import overload
@overload
def foo(value: None) -> None:
pass
def foo(value):
return None
@overload
def foo(value: int) -> str:
pass
def foo(value):
return None
@overload
def foo(value: str) -> str:
pass

View File

@@ -0,0 +1,19 @@
from typing import overload
class A:
@overload
def foo(self, value: None) -> None:
pass
@overload
def foo(self, value: int) -> str:
pass
@overload
def foo(self, value: str) -> str:
pass
A().foo("abc")
<ref>

View File

@@ -0,0 +1,5 @@
from OverloadsAndNoImplementationInImportedClassDep import A
A().foo("abc")
<ref>

View File

@@ -0,0 +1,15 @@
from typing import overload
class A:
@overload
def foo(self, value: None) -> None:
pass
@overload
def foo(self, value: int) -> str:
pass
@overload
def foo(self, value: str) -> str:
pass

View File

@@ -0,0 +1,5 @@
from OverloadsAndNoImplementationInImportedModuleDep import foo
foo("abc")
<ref>

View File

@@ -0,0 +1,16 @@
from typing import overload
@overload
def foo(value: None) -> None:
pass
@overload
def foo(value: int) -> str:
pass
@overload
def foo(value: str) -> str:
pass

View File

@@ -0,0 +1,24 @@
from typing import overload
@overload
def foo(value: None) -> None:
pass
@overload
def foo(value: int) -> str:
pass
@overload
def foo(value: str) -> str:
pass
def foo(value):
return None
foo("abc")
<ref>

View File

@@ -0,0 +1,28 @@
from typing import overload
@overload
def foo(value: None) -> None:
pass
def foo(value):
return None
@overload
def foo(value: int) -> str:
pass
def foo(value):
return None
@overload
def foo(value: str) -> str:
pass
foo("abc")
<ref>

View File

@@ -0,0 +1,20 @@
from typing import overload
@overload
def foo(value: None) -> None:
pass
@overload
def foo(value: int) -> str:
pass
@overload
def foo(value: str) -> str:
pass
foo("abc")
<ref>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,10 @@ import com.intellij.testFramework.LightProjectDescriptor;
import com.jetbrains.python.fixtures.PyResolveTestCase;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.pyi.PyiTypeProvider;
/**
* @author yole
@@ -364,4 +367,230 @@ public class Py3ResolveTest extends PyResolveTestCase {
public void testLocalVariableAnnotationWithInnerClass() {
runWithLanguageLevel(LanguageLevel.PYTHON36, () -> assertResolvesTo(PyClass.class, "MyType"));
}
// PY-22971
public void testOverloadsAndNoImplementationInClass() {
// resolve to the first overload
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final PyFunction foo = assertResolvesTo(PyFunction.class, "foo");
final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), myFixture.getFile());
PyiTypeProvider
.getOverloads(foo, context)
.forEach(
overload -> {
if (overload != foo) assertTrue(PyPsiUtils.isBefore(foo, overload));
}
);
}
);
}
// PY-22971
public void testOverloadsAndImplementationInClass() {
// resolve to the implementation
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final PyFunction foo = assertResolvesTo(PyFunction.class, "foo");
final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), myFixture.getFile());
assertFalse(PyiTypeProvider.isOverload(foo, context));
}
);
}
// PY-22971
public void testOverloadsAndImplementationsInClass() {
// resolve to the first implementation
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final PyFunction foo = assertResolvesTo(PyFunction.class, "foo");
final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), myFixture.getFile());
assertFalse(PyiTypeProvider.isOverload(foo, context));
final PyClass pyClass = foo.getContainingClass();
assertNotNull(pyClass);
pyClass.visitMethods(
function -> {
assertTrue(PyiTypeProvider.isOverload(function, context) || function == foo || PyPsiUtils.isBefore(foo, function));
return true;
},
false,
context
);
}
);
}
// PY-22971
public void testTopLevelOverloadsAndNoImplementation() {
// resolve to the last overload
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final PyFunction foo = assertResolvesTo(PyFunction.class, "foo");
final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), myFixture.getFile());
PyiTypeProvider
.getOverloads(foo, context)
.forEach(
overload -> {
if (overload != foo) assertTrue(PyPsiUtils.isBefore(overload, foo));
}
);
}
);
}
// PY-22971
public void testTopLevelOverloadsAndImplementation() {
// resolve to the implementation
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final PyFunction foo = assertResolvesTo(PyFunction.class, "foo");
final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), myFixture.getFile());
assertFalse(PyiTypeProvider.isOverload(foo, context));
}
);
}
// PY-22971
public void testTopLevelOverloadsAndImplementations() {
// resolve to the last overload
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final PyFunction foo = assertResolvesTo(PyFunction.class, "foo");
final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), myFixture.getFile());
assertTrue(PyiTypeProvider.isOverload(foo, context));
((PyFile)foo.getContainingFile())
.getTopLevelFunctions()
.forEach(
function -> assertTrue(function == foo || PyPsiUtils.isBefore(function, foo))
);
}
);
}
// PY-22971
public void testOverloadsAndNoImplementationInImportedClass() {
// resolve to the first overload
myFixture.copyDirectoryToProject("resolve/OverloadsAndNoImplementationInImportedClassDep", "OverloadsAndNoImplementationInImportedClassDep");
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final PyFunction foo = assertResolvesTo(PyFunction.class, "foo");
final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), myFixture.getFile());
PyiTypeProvider
.getOverloads(foo, context)
.forEach(
overload -> {
if (overload != foo) assertTrue(PyPsiUtils.isBefore(foo, overload));
}
);
}
);
}
// PY-22971
public void testOverloadsAndImplementationInImportedClass() {
// resolve to the implementation
myFixture.copyDirectoryToProject("resolve/OverloadsAndImplementationInImportedClassDep", "OverloadsAndImplementationInImportedClassDep");
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final PyFunction foo = assertResolvesTo(PyFunction.class, "foo");
final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), myFixture.getFile());
assertFalse(PyiTypeProvider.isOverload(foo, context));
}
);
}
// PY-22971
public void testOverloadsAndImplementationsInImportedClass() {
// resolve to the first implementation
myFixture.copyDirectoryToProject("resolve/OverloadsAndImplementationsInImportedClassDep", "OverloadsAndImplementationsInImportedClassDep");
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final PyFunction foo = assertResolvesTo(PyFunction.class, "foo");
final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), myFixture.getFile());
assertFalse(PyiTypeProvider.isOverload(foo, context));
final PyClass pyClass = foo.getContainingClass();
assertNotNull(pyClass);
pyClass.visitMethods(
function -> {
assertTrue(PyiTypeProvider.isOverload(function, context) || function == foo || PyPsiUtils.isBefore(foo, function));
return true;
},
false,
context
);
}
);
}
// PY-22971
public void testOverloadsAndNoImplementationInImportedModule() {
// resolve to the last overload
myFixture.copyDirectoryToProject("resolve/OverloadsAndNoImplementationInImportedModuleDep", "OverloadsAndNoImplementationInImportedModuleDep");
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final PyFunction foo = assertResolvesTo(PyFunction.class, "foo");
final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), myFixture.getFile());
PyiTypeProvider
.getOverloads(foo, context)
.forEach(
overload -> {
if (overload != foo) assertTrue(PyPsiUtils.isBefore(overload, foo));
}
);
}
);
}
// PY-22971
public void testOverloadsAndImplementationInImportedModule() {
// resolve to the implementation
myFixture.copyDirectoryToProject("resolve/OverloadsAndImplementationInImportedModuleDep", "OverloadsAndImplementationInImportedModuleDep");
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final PyFunction foo = assertResolvesTo(PyFunction.class, "foo");
final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), myFixture.getFile());
assertFalse(PyiTypeProvider.isOverload(foo, context));
}
);
}
// PY-22971
public void testOverloadsAndImplementationsInImportedModule() {
// resolve to the last implementation
myFixture.copyDirectoryToProject("resolve/OverloadsAndImplementationsInImportedModuleDep", "OverloadsAndImplementationsInImportedModuleDep");
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final PyFunction foo = assertResolvesTo(PyFunction.class, "foo");
final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), myFixture.getFile());
assertFalse(PyiTypeProvider.isOverload(foo, context));
((PyFile)foo.getContainingFile())
.getTopLevelFunctions()
.forEach(
function -> assertTrue(PyiTypeProvider.isOverload(function, context) || function == foo || PyPsiUtils.isBefore(function, foo))
);
}
);
}
}