mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-48338 PY-60104 Make decorators pure functions if not annotated
GitOrigin-RevId: fed5c22c9114a5dcfb882be367c570baf8ba3e5a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3a3f7234b3
commit
52f21cc60d
@@ -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),
|
||||
|
||||
+59
-10
@@ -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>))
|
||||
+18
@@ -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
|
||||
+11
@@ -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
|
||||
+15
@@ -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
|
||||
+11
@@ -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
|
||||
+15
@@ -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("""
|
||||
|
||||
Reference in New Issue
Block a user