Skip to content

Commit 83f3482

Browse files
committed
Allow self binding for generic ParamSpec
1 parent 55a0812 commit 83f3482

File tree

2 files changed

+175
-1
lines changed

2 files changed

+175
-1
lines changed

mypy/checkmember.py

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
LiteralType,
5858
NoneType,
5959
Overloaded,
60+
Parameters,
6061
ParamSpecType,
6162
PartialType,
6263
ProperType,
@@ -782,6 +783,33 @@ def analyze_var(
782783
else:
783784
call_type = typ
784785

786+
if isinstance(call_type, Instance) and any(
787+
isinstance(arg, Parameters) for arg in call_type.args
788+
):
789+
args: list[Type] = []
790+
for arg in call_type.args:
791+
if not isinstance(arg, Parameters):
792+
args.append(arg)
793+
continue
794+
c = callable_type_from_parameters(arg, mx.chk.named_type("builtins.function"))
795+
if not var.is_staticmethod:
796+
functype: FunctionLike = c
797+
dispatched_type = meet.meet_types(mx.original_type, itype)
798+
signature = freshen_all_functions_type_vars(functype)
799+
bound = get_proper_type(expand_self_type(var, signature, mx.original_type))
800+
assert isinstance(bound, FunctionLike)
801+
signature = bound
802+
signature = check_self_arg(
803+
signature, dispatched_type, var.is_classmethod, mx.context, name, mx.msg
804+
)
805+
signature = bind_self(signature, mx.self_type, var.is_classmethod)
806+
expanded_signature = expand_type_by_instance(signature, itype)
807+
freeze_all_type_vars(expanded_signature)
808+
assert isinstance(expanded_signature, CallableType)
809+
arg = update_parameters_from_signature(arg, expanded_signature)
810+
args.append(arg)
811+
call_type = call_type.copy_modified(args=args)
812+
result = call_type
785813
if isinstance(call_type, FunctionLike) and not call_type.is_type_obj():
786814
if mx.is_lvalue:
787815
if var.is_property:
@@ -793,7 +821,7 @@ def analyze_var(
793821
if not var.is_staticmethod:
794822
# Class-level function objects and classmethods become bound methods:
795823
# the former to the instance, the latter to the class.
796-
functype: FunctionLike = call_type
824+
functype = call_type
797825
# Use meet to narrow original_type to the dispatched type.
798826
# For example, assume
799827
# * A.f: Callable[[A1], None] where A1 <: A (maybe A1 == A)
@@ -1094,6 +1122,30 @@ def analyze_class_attribute_access(
10941122
isinstance(node.node, FuncBase) and node.node.is_static
10951123
)
10961124
t = get_proper_type(t)
1125+
if isinstance(t, Instance) and any(isinstance(arg, Parameters) for arg in t.args):
1126+
args: list[Type] = []
1127+
for arg in t.args:
1128+
if not isinstance(arg, Parameters):
1129+
args.append(arg)
1130+
continue
1131+
c: FunctionLike = callable_type_from_parameters(
1132+
arg, mx.chk.named_type("builtins.function")
1133+
)
1134+
if is_classmethod:
1135+
c = check_self_arg(c, mx.self_type, False, mx.context, name, mx.msg)
1136+
res = add_class_tvars(
1137+
c,
1138+
isuper,
1139+
is_classmethod,
1140+
is_staticmethod,
1141+
mx.self_type,
1142+
original_vars=original_vars,
1143+
)
1144+
signature = get_proper_type(res)
1145+
assert isinstance(signature, CallableType)
1146+
arg = update_parameters_from_signature(arg, signature)
1147+
args.append(arg)
1148+
t = t.copy_modified(args=args)
10971149
if isinstance(t, FunctionLike) and is_classmethod:
10981150
t = check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg)
10991151
result = add_class_tvars(
@@ -1392,3 +1444,30 @@ def is_valid_constructor(n: SymbolNode | None) -> bool:
13921444
if isinstance(n, Decorator):
13931445
return isinstance(get_proper_type(n.type), FunctionLike)
13941446
return False
1447+
1448+
1449+
def callable_type_from_parameters(
1450+
param: Parameters, fallback: Instance, ret_type: Type | None = None
1451+
) -> CallableType:
1452+
"""Create CallableType from Parameters."""
1453+
return CallableType(
1454+
arg_types=param.arg_types,
1455+
arg_kinds=param.arg_kinds,
1456+
arg_names=param.arg_names,
1457+
ret_type=ret_type if ret_type is not None else NoneType(),
1458+
fallback=fallback,
1459+
variables=param.variables,
1460+
imprecise_arg_kinds=param.imprecise_arg_kinds,
1461+
)
1462+
1463+
1464+
def update_parameters_from_signature(param: Parameters, signature: CallableType) -> Parameters:
1465+
"""Update Parameters from signature."""
1466+
return param.copy_modified(
1467+
arg_types=signature.arg_types,
1468+
arg_kinds=signature.arg_kinds,
1469+
arg_names=signature.arg_names,
1470+
is_ellipsis_args=signature.is_ellipsis_args,
1471+
variables=signature.variables,
1472+
imprecise_arg_kinds=signature.imprecise_arg_kinds,
1473+
)

test-data/unit/check-selftype.test

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2132,3 +2132,98 @@ class D:
21322132
x: int
21332133
x: Union[C, D]
21342134
reveal_type(x.x) # N: Revealed type is "Union[__main__.C, builtins.int]"
2135+
2136+
[case testSelfTypeBindingWithGenericParamSpec]
2137+
from typing import Generic, Callable
2138+
from typing_extensions import ParamSpec
2139+
2140+
P = ParamSpec("P")
2141+
2142+
class Wrapper(Generic[P]):
2143+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None: ...
2144+
2145+
def decorator(f: Callable[P, None]) -> Callable[P, None]: ...
2146+
def lru_cache(f: Callable[P, None]) -> Wrapper[P]: ...
2147+
2148+
class A:
2149+
@decorator
2150+
def method1(self, val: int) -> None: ...
2151+
2152+
@lru_cache
2153+
def method2(self, val: int) -> None: ...
2154+
2155+
def test(self) -> None:
2156+
reveal_type(self.method1) # N: Revealed type is "def (val: builtins.int)"
2157+
reveal_type(self.method2) # N: Revealed type is "__main__.Wrapper[[val: builtins.int]]"
2158+
reveal_type(A.method1) # N: Revealed type is "def (self: __main__.A, val: builtins.int)"
2159+
reveal_type(A.method2) # N: Revealed type is "__main__.Wrapper[[self: __main__.A, val: builtins.int]]"
2160+
2161+
self.method1(2)
2162+
self.method2(2)
2163+
[builtins fixtures/tuple.pyi]
2164+
2165+
[case testSelfTypeBindingWithGenericParamSpecClassmethod]
2166+
from typing import Generic, Callable
2167+
from typing_extensions import ParamSpec
2168+
2169+
P = ParamSpec("P")
2170+
2171+
class Wrapper(Generic[P]):
2172+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None: ...
2173+
2174+
def decorator(f: Callable[P, None]) -> Callable[P, None]: ...
2175+
def lru_cache(f: Callable[P, None]) -> Wrapper[P]: ...
2176+
2177+
class A:
2178+
@classmethod
2179+
@decorator
2180+
def method1(cls, val: int) -> None: ...
2181+
2182+
@classmethod
2183+
@lru_cache
2184+
def method2(cls, val: int) -> None: ...
2185+
2186+
def test(self) -> None:
2187+
reveal_type(self.method1) # N: Revealed type is "def (val: builtins.int)"
2188+
reveal_type(self.method2) # N: Revealed type is "__main__.Wrapper[[val: builtins.int]]"
2189+
reveal_type(A.method1) # N: Revealed type is "def (val: builtins.int)"
2190+
reveal_type(A.method2) # N: Revealed type is "__main__.Wrapper[[val: builtins.int]]"
2191+
2192+
self.method1(2)
2193+
self.method2(2)
2194+
A.method1(2)
2195+
A.method2(2)
2196+
[builtins fixtures/classmethod.pyi]
2197+
2198+
[case testSelfTypeBindingWithGenericParamSpecStaticmethod]
2199+
from typing import Generic, Callable
2200+
from typing_extensions import ParamSpec
2201+
2202+
P = ParamSpec("P")
2203+
2204+
class Wrapper(Generic[P]):
2205+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None: ...
2206+
2207+
def decorator(f: Callable[P, None]) -> Callable[P, None]: ...
2208+
def lru_cache(f: Callable[P, None]) -> Wrapper[P]: ...
2209+
2210+
class A:
2211+
@staticmethod
2212+
@decorator
2213+
def method1(val: int) -> None: ...
2214+
2215+
@staticmethod
2216+
@lru_cache
2217+
def method2(val: int) -> None: ...
2218+
2219+
def test(self) -> None:
2220+
reveal_type(self.method1) # N: Revealed type is "def (val: builtins.int)"
2221+
reveal_type(self.method2) # N: Revealed type is "__main__.Wrapper[[val: builtins.int]]"
2222+
reveal_type(A.method1) # N: Revealed type is "def (val: builtins.int)"
2223+
reveal_type(A.method2) # N: Revealed type is "__main__.Wrapper[[val: builtins.int]]"
2224+
2225+
self.method1(2)
2226+
self.method2(2)
2227+
A.method1(2)
2228+
A.method2(2)
2229+
[builtins fixtures/classmethod.pyi]

0 commit comments

Comments
 (0)