PY-48338 PY-60104 Make decorators pure functions if not annotated

GitOrigin-RevId: fed5c22c9114a5dcfb882be367c570baf8ba3e5a
This commit is contained in:
andrey.matveev
2024-01-09 20:49:13 +00:00
committed by intellij-monorepo-bot
parent 3a3f7234b3
commit 52f21cc60d
44 changed files with 1005 additions and 26 deletions
@@ -514,6 +514,7 @@
<dialectsTokenSetContributor implementation="com.jetbrains.python.PythonTokenSetContributor"/>
<typeProvider implementation="com.jetbrains.python.psi.types.PyCollectionTypeByModificationsProvider" order="last"/>
<typeProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyDataclassTypeProvider"/>
<typeProvider implementation="com.jetbrains.python.codeInsight.decorator.PyDecoratedFunctionTypeProvider"/>
@@ -557,7 +558,6 @@
<!--stdlib-->
<canonicalPathProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyStdlibCanonicalPathProvider"/>
<inspectionExtension implementation="com.jetbrains.python.inspections.stdlib.PyStdlibInspectionExtension"/>
<typeProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyDataclassTypeProvider"/>
<typeProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyNamedTupleTypeProvider"/>
<!-- This provider should be able to override the results on anything else, including types coming from .pyi stubs -->
<typeProvider implementation="com.jetbrains.python.codeInsight.stdlib.PyStdlibTypeProvider" order="first"/>
@@ -91,7 +91,7 @@ object PyDataclassNames {
* It should be used only to map arguments to parameters and
* determine what settings dataclass has.
*/
private val DECORATOR_AND_TYPE_AND_PARAMETERS = listOf(
val DECORATOR_AND_TYPE_AND_PARAMETERS = listOf(
Triple(KnownDecorator.DATACLASSES_DATACLASS, PyDataclassParameters.PredefinedType.STD, PyDataclassNames.Dataclasses.DECORATOR_PARAMETERS),
Triple(KnownDecorator.ATTR_S, PyDataclassParameters.PredefinedType.ATTRS, PyDataclassNames.Attrs.DECORATOR_PARAMETERS),
Triple(KnownDecorator.ATTR_ATTRS, PyDataclassParameters.PredefinedType.ATTRS, PyDataclassNames.Attrs.DECORATOR_PARAMETERS),
@@ -6,13 +6,19 @@ import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.PyTypeProviderBase;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static com.jetbrains.python.psi.types.PyTypeUtil.notNullToRef;
@@ -32,12 +38,18 @@ public final class PyDecoratedFunctionTypeProvider extends PyTypeProviderBase {
if (decoratorList == null) {
return null;
}
List<PyDecorator> decorators = ContainerUtil.filter(decoratorList.getDecorators(), d ->
PyKnownDecoratorUtil.asKnownDecorators(d, context).isEmpty()
);
if (decorators.isEmpty()) {
return null;
}
List<PyKnownDecoratorUtil.KnownDecorator> filteredDecorators = new ArrayList<>();
filteredDecorators.add(PyKnownDecoratorUtil.KnownDecorator.TYPING_OVERLOAD);
filteredDecorators.add(PyKnownDecoratorUtil.KnownDecorator.STATICMETHOD);
filteredDecorators.add(PyKnownDecoratorUtil.KnownDecorator.CLASSMETHOD);
var decorators = Arrays.stream(decoratorList.getDecorators()).toList();
var haveSpecialCase = ContainerUtil.exists(decorators, d ->
ContainerUtil.exists(PyKnownDecoratorUtil.asKnownDecorators(d, context),
it -> ContainerUtil.exists(filteredDecorators, filtered -> filtered.equals(it))));
if (haveSpecialCase) return null;
/* Our goal is to infer the type of reference of decorated object.
* For that we going to infer a type of expression <code>decorator(reference)<code>.
@@ -55,11 +67,48 @@ public final class PyDecoratedFunctionTypeProvider extends PyTypeProviderBase {
private static Ref<PyType> evaluateType(@NotNull PyDecoratable referenceTarget,
@NotNull TypeEvalContext context,
@NotNull List<PyDecorator> decorators) {
PyExpression fakeCallExpression = fakeCallExpression(referenceTarget, decorators, context);
if (fakeCallExpression == null) {
return null;
PyType sourceType = null;
if (referenceTarget instanceof PyTypedElement typedElement) {
sourceType = context.getType(typedElement);
}
return notNullToRef(context.getType(fakeCallExpression));
var annotatedDecorators = getAnnotatedDecorators(referenceTarget, decorators, context);
if (!annotatedDecorators.isEmpty()) {
PyExpression fakeCallExpression = fakeCallExpression(referenceTarget, annotatedDecorators, context);
if (fakeCallExpression == null) {
return null;
}
var fakeCallExpressionType = context.getType(fakeCallExpression);
return notNullToRef(fakeCallExpressionType);
}
return notNullToRef(sourceType);
}
@NotNull
private static List<PyDecorator> getAnnotatedDecorators(@NotNull PyDecoratable referenceTarget,
@NotNull List<PyDecorator> decorators,
@NotNull TypeEvalContext context) {
var result = new ArrayList<PyDecorator>();
var scopeOwner = ScopeUtil.getScopeOwner(referenceTarget);
if (scopeOwner == null) return Collections.emptyList();
for (var decorator : decorators) {
var qualifiedName = decorator.getQualifiedName();
if (qualifiedName == null) continue;
var resolved = PyResolveUtil.resolveQualifiedNameInScope(qualifiedName, scopeOwner, context);
for (var res : resolved) {
if (res instanceof PyFunction function) {
var annotation = PyTypingTypeProvider.getReturnTypeAnnotation(function, context);
if (annotation != null) {
result.add(decorator);
}
}
else if (res instanceof PyClass) {
result.add(decorator);
}
}
}
return result;
}
@Nullable
@@ -0,0 +1,19 @@
from dataclasses import dataclass
def new_init(self, bar):
self.bar = bar
def dec(cls):
cls.__init__ = new_init
return cls
@dec
@dataclass
class Foo:
bar: int
Foo(<arg1>)
@@ -0,0 +1,17 @@
import functools
from typing import ParamSpec, TypeVar, Callable
def decorator(func) -> Callable[[int], str]:
def wrapper(*args, **kwargs):
return ""
return wrapper
@decorator
def function(input_a: int, input_b: float) -> float:
return input_a + input_b
print(function(<arg1>))
@@ -0,0 +1,18 @@
import functools
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("using_decorator")
return func(42, 42.0)
return wrapper
@decorator
def function(input_a: int, input_b: float):
return input_a + input_b
if __name__ == '__main__':
function(<arg1>)
@@ -0,0 +1,14 @@
def deco(func):
def wrapper():
func()
return wrapper
@deco
def bar():
pass
bar(<arg1>)
@@ -0,0 +1,18 @@
import functools
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("using_decorator")
return func(*args, **kwargs)
return wrapper
@decorator
def function(input_a: int, input_b: float):
return input_a + input_b
if __name__ == '__main__':
function(<arg1>)
@@ -0,0 +1,22 @@
import functools
from typing import ParamSpec, TypeVar, Callable
P = ParamSpec("P")
R = TypeVar("R")
def decorator(func: Callable[P, R]) -> Callable[P, R]:
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("using_decorator")
return func(1, 2)
return wrapper
@decorator
def function(input_a: int, input_b: float) -> float:
return input_a + input_b
print(function(<arg1>))
@@ -0,0 +1,27 @@
import functools
def decorator1(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("using_decorator1")
return func(*args, **kwargs)
return wrapper
def decorator2(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("using_decorator2")
return func(*args, **kwargs)
return wrapper
@decorator1
@decorator2
def function(input_a: int, input_b: float):
return input_a + input_b
if __name__ == '__main__':
function(<arg1>)
@@ -0,0 +1,26 @@
import functools
def unknown_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("using_decorator")
return func(*args, **kwargs)
return wrapper
def decorator(func):
@unknown_decorator
def wrapper(*args, **kwargs):
print("using_decorator")
return func(*args, **kwargs)
return wrapper
@decorator
def function(input_a: int, input_b: float):
return input_a + input_b
if __name__ == '__main__':
function(<arg1>)
@@ -0,0 +1,23 @@
import functools
from typing import Callable, ParamSpec, TypeVar, Concatenate
P = ParamSpec("P")
R = TypeVar("R")
def decorator(func: Callable[P, R]) -> Callable[Concatenate[str, P], R]:
@functools.wraps(func)
def wrapper(input_c: str, input_a: int, input_b: float):
print("using_decorator")
return func(input_a, input_b)
return wrapper
@decorator
def function(input_a: int, input_b: float):
return input_a + input_b
if __name__ == '__main__':
function(<arg1>)
@@ -0,0 +1,21 @@
from functools import wraps
import inspect
class Route:
def __init__(self, input_a: int, input_b: float):
...
class Router:
def __init__(self):
self.routes = []
@wraps(Route.__init__)
def route(self, *args, **kwargs):
route = Route(*args, **kwargs)
self.routes.append(route)
r = Router()
r.route(<arg1>)
@@ -0,0 +1,9 @@
import functools
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("using_decorator1")
return func(*args, **kwargs)
return wrapper
@@ -0,0 +1,14 @@
import functools
from typing import ParamSpec, TypeVar, Callable
P = ParamSpec("P")
R = TypeVar("R")
def decorator(func: Callable[P, R]) -> Callable[P, R]:
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("using_decorator")
return func(1, 2)
return wrapper
@@ -0,0 +1,8 @@
from ext import dec_no_annotation
@dec_no_annotation
def func(i: str) -> int:
return 1
value = func(1, True)
dec_func = func
@@ -0,0 +1,6 @@
from typing import Callable
def dec_no_annotation(fun):
def wrapper(i: int, b: bool):
return ""
return wrapper
@@ -0,0 +1,8 @@
from ext import dec_annotation
@dec_annotation
def func(i: str) -> int:
return 1
value = func(1, True)
dec_func = func
@@ -0,0 +1,6 @@
from typing import Callable
def dec_annotation(fun) -> Callable[[int, bool], str]:
def wrapper(i: int, b: bool):
return ""
return wrapper
@@ -0,0 +1,8 @@
from ext import dec_not_change
@dec_not_change
def func(name_in_func: str) -> int:
return 1
value = func("")
dec_func = func
@@ -0,0 +1,6 @@
from typing import Callable
def dec_not_change(fun):
def wrapper(name_in_wrapper: str):
return 42
return wrapper
@@ -0,0 +1,8 @@
from ext import dec_not_change
@dec_not_change
def func(i: str) -> int:
return 1
value = func("")
dec_func = func
@@ -0,0 +1,6 @@
from typing import Callable
def dec_not_change(fun) -> Callable[[str], int]:
def wrapper(s: str):
return 42
return wrapper
@@ -0,0 +1,11 @@
from ext import first_dec_change, second_dec_same
@second_dec_same
@first_dec_change
def func(i: str) -> int:
return 1
value = func("")
dec_func = func
@@ -0,0 +1,10 @@
from typing import Callable
def first_dec_change(fun):
def wrapper(i: int, b: bool):
return ""
return wrapper
def second_dec_same(fun):
return fun
@@ -0,0 +1,11 @@
from ext import first_dec_change, second_dec_same
@second_dec_same
@first_dec_change
def func(i: str) -> int:
return 1
value = func(1, True)
dec_func = func
@@ -0,0 +1,10 @@
from typing import Callable
def first_dec_change(fun) -> Callable[[int, bool], str]:
def wrapper(i: int, b: bool):
return ""
return wrapper
def second_dec_same(fun):
return fun
@@ -0,0 +1,11 @@
from ext import first_dec_change, second_dec_same
@second_dec_same
@first_dec_change
def func(i: str) -> int:
return 1
value = func(1, "")
dec_func = func
@@ -0,0 +1,15 @@
from typing import Concatenate, Callable, ParamSpec, TypeVar
P = ParamSpec("P")
R = TypeVar("R")
def first_dec_change(f: Callable[P, R]) -> Callable[Concatenate[int, P], list[R]]:
def inner(aa: int, *args: P.args, **kwargs: P.kwargs) -> list[R]:
return [f(*args, **kwargs)]
return inner
def second_dec_same(fun):
return fun
@@ -0,0 +1,11 @@
from ext import first_dec_same, second_dec_same
@second_dec_same
@first_dec_same
def func(i: str) -> int:
return 1
value = func("")
dec_func = func
@@ -0,0 +1,8 @@
from typing import Callable
def first_dec_same(fun):
return fun
def second_dec_same(fun):
return fun
@@ -0,0 +1,11 @@
from ext import first_dec_same, second_dec_same
@second_dec_same
@first_dec_same
def func(i: str) -> int:
return 1
value = func("")
dec_func = func
@@ -0,0 +1,8 @@
from typing import Callable
def first_dec_same(fun) -> Callable[[str], int]:
return fun
def second_dec_same(fun) -> Callable[[str], int]:
return fun
@@ -0,0 +1,11 @@
from ext import first_dec_same, second_dec_change
@second_dec_change
@first_dec_same
def func(i: str) -> int:
return 1
value = func("42")
dec_func = func
@@ -0,0 +1,10 @@
from typing import Callable
def second_dec_change(fun):
def wrapper(i: int, b: bool):
return ""
return wrapper
def first_dec_same(fun):
return fun
@@ -0,0 +1,11 @@
from ext import first_dec_same, second_dec_change
@second_dec_change
@first_dec_same
def func(i: str) -> int:
return 1
value = func(42, True)
dec_func = func
@@ -0,0 +1,10 @@
from typing import Callable
def second_dec_change(fun) -> Callable[[int, bool], str]:
def wrapper(i: int, b: bool):
return ""
return wrapper
def first_dec_same(fun):
return fun
@@ -0,0 +1,11 @@
from ext import first_dec_same, second_dec_change
@second_dec_change
@first_dec_same
def func(i: str) -> int:
return 1
value = func(42, "")
dec_func = func
@@ -0,0 +1,15 @@
from typing import Concatenate, Callable, ParamSpec, TypeVar
P = ParamSpec("P")
R = TypeVar("R")
def second_dec_change(f: Callable[P, R]) -> Callable[Concatenate[int, P], list[R]]:
def inner(aa: int, *args: P.args, **kwargs: P.kwargs) -> list[R]:
return [f(*args, **kwargs)]
return inner
def first_dec_same(fun):
return fun
@@ -322,6 +322,74 @@ public class Py3CompletionTest extends PyTestCase {
assertDoesntContain(suggested, "meta_method");
}
// PY-23067
public void testFunctoolsWraps() {
final List<String> suggested = doTestByText("""
import functools
def raise_python_exceptions(func):
@functools.wraps(func)
def decorator(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
raise
return decorator
class Test(object):
def __init__(self):
self.a = 1
@raise_python_exceptions
def get_test_str(self, extra:str) -> str:
return "test " + extra
class Test2(object):
def __init__(self):
self.a = 1
@raise_python_exceptions
def get_the_test(self):
return Test()
Test2().get_the_test().<caret>""");
assertNotNull(suggested);
assertContainsElements(suggested, "get_test_str");
}
// PY-23067
public void _testFunctoolsWrapsElementMetadata() {
myFixture.configureByText(PythonFileType.INSTANCE, """
from functools import wraps
class Route:
def __init__(self, url='localhost', port=80):
...
class Router:
@wraps(Route.__init__)
def route(self, *args, **kwargs):
route = Route(*args, **kwargs)
self.routes.append(route)
router = Router()
router.<caret>
""");
myFixture.completeBasic();
LookupElement[] variants = myFixture.completeBasic();
assertNotNull(variants);
LookupElement lookupElement = ContainerUtil.find(variants, v -> v.getLookupString().equals("route"));
assertNotNull(lookupElement);
assertEquals("(self, url, port)", TestLookupElementPresentation.renderElement(lookupElement).getTailText());
}
// PY-27398
public void testDataclassPostInit() {
doMultiFileTest();
@@ -1561,6 +1561,222 @@ public class Py3TypeTest extends PyTestCase {
""");
}
// PY-48338
public void testDecoratedFunctionHasOriginalFunctionType() {
doTest("(input_a: int, input_b: float) -> float | Any",
"import functools\n" +
"\n" +
"def decorator(func):\n" +
" @functools.wraps(func)\n" +
" def wrapper(*args, **kwargs):\n" +
" print(\"using_decorator\")\n" +
" return func(*args, **kwargs)\n" +
" return wrapper\n" +
"\n" +
"\n" +
"@decorator\n" +
"def function(input_a: int, input_b: float):\n" +
" return input_a + input_b\n" +
"\n" +
"\n" +
"if __name__ == '__main__':\n" +
" expr = function\n");
}
// PY-48338
public void testDecoratedFromOtherFileFunctionHasOriginalFunctionType() {
doMultiFileTest("(input_a: int, input_b: float) -> float | Any",
"import functools\n" +
"from dec_mod import decorator\n" +
"\n" +
"\n" +
"@decorator\n" +
"def function(input_a: int, input_b: float):\n" +
" return input_a + input_b\n" +
"\n" +
"\n" +
"if __name__ == '__main__':\n" +
" expr = function");
}
// PY-48338
public void testTwiceDecoratedFunctionHasUnionOfOriginalFunctionAndUnknownCallableType() {
doTest("(input_a: int, input_b: float) -> float | Any",
"import functools\n" +
"\n" +
"\n" +
"def decorator1(func):\n" +
" @functools.wraps(func)\n" +
" def wrapper(*args, **kwargs):\n" +
" print(\"using_decorator1\")\n" +
" return func(*args, **kwargs)\n" +
" return wrapper\n" +
"\n" +
"\n" +
"def decorator2(func):\n" +
" @functools.wraps(func)\n" +
" def wrapper(*args, **kwargs):\n" +
" print(\"using_decorator2\")\n" +
" return func(*args, **kwargs)\n" +
" return wrapper\n" +
"\n" +
"\n" +
"@decorator1\n" +
"@decorator2\n" +
"def function(input_a: int, input_b: float):\n" +
" return input_a + input_b\n" +
"\n" +
"\n" +
"if __name__ == '__main__':\n" +
" expr = function");
}
// PY-48338
public void testDecoratedFunctionWithUnknownWrapperDecoratorHasUnionOriginalFunctionAndUnknownCallableType() {
doTest("(input_a: int, input_b: float) -> float | Any",
"import functools\n" +
"\n" +
"\n" +
"def unknown_decorator(func):\n" +
" @functools.wraps(func)\n" +
" def wrapper(*args, **kwargs):\n" +
" print(\"using_decorator\")\n" +
" return func(*args, **kwargs)\n" +
" return wrapper\n" +
"\n" +
"\n" +
"def decorator(func):\n" +
" @unknown_decorator\n" +
" def wrapper(*args, **kwargs):\n" +
" print(\"using_decorator\")\n" +
" return func(*args, **kwargs)\n" +
" return wrapper\n" +
"\n" +
"\n" +
"@decorator\n" +
"def function(input_a: int, input_b: float):\n" +
" return input_a + input_b\n" +
"\n" +
"\n" +
"if __name__ == '__main__':\n" +
" expr = function");
}
// PY-48338
public void testDecoratedFunctionWithOtherWrapperParametersHasWrapperType() {
doTest("(str, input_a: int, input_b: float) -> float | Any",
"import functools\n" +
"from typing import Callable, ParamSpec, TypeVar, Concatenate\n" +
"P = ParamSpec(\"P\")\n" +
"R = TypeVar(\"R\")\n" +
"\n" +
"\n" +
"def decorator(func: Callable[P, R]) -> Callable[[Concatenate[str, P]], R]:\n" +
" @functools.wraps(func)\n" +
" def wrapper(input_c: str, input_a: int, input_b: float):\n" +
" print(\"using_decorator\")\n" +
" return func(input_a, input_b)\n" +
" return wrapper\n" +
"\n" +
"\n" +
"@decorator\n" +
"def function(input_a: int, input_b: float):\n" +
" return input_a + input_b\n" +
"\n" +
"\n" +
"if __name__ == '__main__':\n" +
" expr = function");
}
// PY-48338
public void testDecoratedFunctionHasTypeFromParamSpec() {
doTest("(input_a: int, input_b: float) -> float",
"import functools\n" +
"from typing import ParamSpec, TypeVar, Callable\n" +
"\n" +
"P = ParamSpec(\"P\")\n" +
"R = TypeVar(\"R\")\n" +
"\n" +
"\n" +
"def decorator(func: Callable[P, R]) -> Callable[P, R]:\n" +
" @functools.wraps(func)\n" +
" def wrapper(*args, **kwargs):\n" +
" print(\"using_decorator\")\n" +
" return func(1, 2)\n" +
"\n" +
" return wrapper\n" +
"\n" +
"\n" +
"@decorator\n" +
"def function(input_a: int, input_b: float) -> float:\n" +
" return input_a + input_b\n" +
"\n" +
"\n" +
"expr = function");
}
// PY-48338
public void testDecoratedFromOtherFileFunctionHasTypeFromParamSpec() {
doMultiFileTest("(input_a: int, input_b: float) -> float",
"import functools\n" +
"from dec_mod import decorator\n"+
"\n" +
"\n" +
"@decorator\n" +
"def function(input_a: int, input_b: float) -> float:\n" +
" return input_a + input_b\n" +
"\n" +
"\n" +
"expr = function");
}
// PY-60104
public void testDecoratedFunctionChangedTypeFromParamSpec() {
doTest("(str, a: int, b: int) -> None",
"""
import functools
from typing import Any, Callable, Concatenate, ParamSpec, reveal_type, TypeVar
P = ParamSpec('P')
T = TypeVar('T')
def changing_signature(f: Callable[P, T]) -> Callable[Concatenate[str, P], T]:
@functools.wraps(f)
def wrapper(ignored: Any, *args: P.args, **kwargs: P.kwargs) -> T:
return f(*args, **kwargs)
return wrapper
@changing_signature
def f(a: int, b: int) -> None:
print(a, b)
expr = f
""");
}
public void testDecoratedClassTypeNoChange() {
doTest("Type[C]", """
from typing import reveal_type
def changing_interface(cls):
cls.new_attr = 42
return cls
@changing_interface
class C:
def __init__(self, p: int) -> None:
self.attr = p
expr = C
""");
}
/**
* @see #testRecursiveDictTopDown()
* @see PyTypeCheckerInspectionTest#testRecursiveDictAttribute()
@@ -14,7 +14,7 @@ public class PyDecoratedFunctionTypeProviderTest extends PyTestCase {
public void testMakeConstant() {
doTest("int", "int",
"""
def dec(fun):
def dec(fun) -> int:
return 12
@dec
@@ -28,7 +28,9 @@ public class PyDecoratedFunctionTypeProviderTest extends PyTestCase {
public void testMakeConstantFunction() {
doTest("int", "() -> int",
"""
def dec(fun):
from typing import Callable
def dec(fun) -> Callable[[], int]:
def wrapper():
return 12
return wrapper
@@ -42,9 +44,11 @@ public class PyDecoratedFunctionTypeProviderTest extends PyTestCase {
}
public void testMakeConstantWithArg() {
doTest("int", "(boo: str) -> int",
doTest("int", "(str) -> int",
"""
def dec(fun):
from typing import Callable
def dec(fun) -> Callable[[str], int]:
def wrapper(boo: str):
return 12
return wrapper
@@ -58,9 +62,11 @@ public class PyDecoratedFunctionTypeProviderTest extends PyTestCase {
}
public void testMakeKnownWithArg() {
doTest("str", "(boo: str) -> str",
doTest("str", "(str) -> str",
"""
def dec(fun):
from typing import Callable
def dec(fun) -> Callable[[str], str]:
def wrapper(boo: str):
return str(fun())
return wrapper
@@ -73,9 +79,11 @@ public class PyDecoratedFunctionTypeProviderTest extends PyTestCase {
}
public void testMakeDecoratorWithArg() {
doTest("str", "(boo: str) -> str",
doTest("str", "(str) -> str",
"""
def dec(i):
from typing import Callable
def dec(i) -> Callable[[Callable[[], float]], Callable[[str], str]]:
def dec_(fun):
def wrapper(boo: str):
return str(fun())
@@ -91,9 +99,11 @@ public class PyDecoratedFunctionTypeProviderTest extends PyTestCase {
}
public void testReferenceInside() {
doTest("str", "(boo: str) -> str",
doTest("str", "(str) -> str",
"""
def dec(i):
from typing import Callable
def dec(i) -> Callable[[Callable[[], float]], Callable[[str], str]]:
def dec_(fun):
def wrapper(boo: str):
return str(fun())
@@ -147,11 +157,12 @@ public class PyDecoratedFunctionTypeProviderTest extends PyTestCase {
}
public void testIgnoreWraps() {
doTest("str", "(boo: str) -> str",
doTest("str", "(str) -> str",
"""
from functools import wraps
from typing import Callable
def dec(fun):
def dec(fun) -> Callable[[str], str]:
@wraps(fun)
def wrapper(boo: str):
print(f'{boo} wrapper')
@@ -190,7 +201,7 @@ public class PyDecoratedFunctionTypeProviderTest extends PyTestCase {
}
public void testMakeDecoratorStack() {
doTest("int", "(tt: str) -> int",
doTest("int", "(str) -> int",
"""
from typing import Callable
from functools import wraps
@@ -202,7 +213,7 @@ public class PyDecoratedFunctionTypeProviderTest extends PyTestCase {
def dec1(t: T) -> Callable[[Callable[[], S]], Callable[[], T]]:
pass
def dec2(t: T):
def dec2(t: T) -> Callable[[Callable[[], S]], Callable[[T], S]]:
def dec(fun: Callable[[], S]):
@wraps(fun)
def wrapper(tt: T) -> S:
@@ -275,6 +286,66 @@ public class PyDecoratedFunctionTypeProviderTest extends PyTestCase {
checkMultiFileTest("str", "(int) -> str", Context.USER_INITIATED);
}
// PY-48338
public void testImportDecoratorNotChangeSignatureWithAnnotation() {
doMultiFileTest("int", "(str) -> int");
}
// PY-48338
public void testImportDecoratorNotChangeSignatureNoAnnotation() {
doMultiFileTest("int", "(name_in_func: str) -> int");
}
// PY-48338
public void testImportDecoratorChangeSignatureNoAnnotation() {
doMultiFileTest("int", "(i: str) -> int");
}
// PY-48338
public void testImportDecoratorChangeSignatureWithAnnotation() {
doMultiFileTest("str", "(int, bool) -> str");
}
// PY-48338
public void testImportTwoDecoratorsFirstChangeSignatureNoAnnotation() {
doMultiFileTest("int", "(i: str) -> int");
}
// PY-48338
public void testImportTwoDecoratorsFirstChangeSignatureWithAnnotation() {
doMultiFileTest("str", "(int, bool) -> str");
}
// PY-48338
public void testImportTwoDecoratorsNotChangeSignatureNoAnnotation() {
doMultiFileTest("int", "(i: str) -> int");
}
// PY-48338
public void testImportTwoDecoratorsNotChangeSignatureWithAnnotation() {
doMultiFileTest("int", "(str) -> int");
}
// PY-48338
public void testImportTwoDecoratorsSecondChangeSignatureNoAnnotation() {
doMultiFileTest("int", "(i: str) -> int");
}
// PY-48338
public void testImportTwoDecoratorsSecondChangeSignatureWithAnnotation() {
doMultiFileTest("str", "(int, bool) -> str");
}
// PY-48338
public void testImportTwoDecoratorsFirstChangeSignatureWithAnnotationParamSpec() {
doMultiFileTest("list[int]", "(int, i: str) -> list[int]");
}
// PY-48338
public void testImportTwoDecoratorsSecondChangeSignatureWithAnnotationParamSpec() {
doMultiFileTest("list[int]", "(int, i: str) -> list[int]");
}
// PY-49935
public void testParamSpec() {
doTest("int", "(x: int, y: str) -> int",
@@ -1061,6 +1061,81 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
ArrayUtilRt.EMPTY_STRING_ARRAY);
}
// PY-48338
public void testDecoratedFunctionParametersFromOriginalFunction() {
final Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("input_a: int, input_b: float",
new String[]{"input_a: int, "},
new String[]{""});
}
// PY-48338
public void testDecoratedFunctionParametersEmptyParameters() {
final Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("<no parameters>",
ArrayUtilRt.EMPTY_STRING_ARRAY,
new String[]{"<no parameters>"});
}
// PY-48338
public void testDecoratedFunctionParametersFromParamSpec() {
final Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("input_a: int, input_b: float",
new String[]{"input_a: int, "},
new String[]{""});
}
// PY-48338
public void testDecoratedFunctionParametersTwoDecorators() {
final Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("input_a: int, input_b: float",
new String[]{"input_a: int, "},
new String[]{""});
}
// PY-48338
public void testDecoratedFunctionParametersCallOriginalWithOtherParameters() {
final Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("input_a: int, input_b: float",
new String[]{"input_a: int, "},
new String[]{""});
}
// PY-48338
public void testDecoratedFunctionParametersWrapperWithOtherParameters() {
final Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("...: str, input_a: int, input_b: float",
new String[]{"...: str, "},
new String[]{""});
}
// PY-48338
public void testDecoratedFunctionParametersWrapperHasUnknownDecorator() {
final Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("input_a: int, input_b: float",
new String[]{"input_a: int, "},
new String[]{""});
}
// PY-48338
public void testDecoratedDataClassParameters() {
final Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("bar: int",
new String[]{"bar: int"},
new String[]{""});
}
// PY-48338
public void testDecoratedFunctionDifferentParametersFromAnnotation() {
final Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("...: int",
new String[]{"...: int"},
new String[]{""});
}
// PY-46053
public void testLongTypeHintReplaceWithAnnotation() {
final Map<String, PsiElement> test = loadTest(2);
@@ -18,6 +18,48 @@ public class Py3ArgumentListInspectionTest extends PyInspectionTestCase {
return ourPyLatestDescriptor;
}
// PY-23067
public void testUnfilledParametersFunctoolsWrapsNoWarning() {
doTestByText("""
from functools import wraps
def foo(a: int, b: str) -> int:
return a + int(b)
@wraps(foo)
def test(*args, **kwargs) -> int:
return a + int(b)
test()
""");
}
// PY-23067
public void testUnfilledParametersFunctoolsWrapsDecoratorWrapperNoWarning() {
doTestByText("""
from functools import wraps
def decorator_without_params(func):
@wraps(func)
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return wrapper
@decorator_without_params
def test(a: int, b: str) -> int:
return a + int(b)
test()
""");
}
// PY-36158
public void testDataclassesStarImportNoUnexpectedArgumentWarning() {
doTestByText("""