Skip to content

Commit fd4a62a

Browse files
committed
Allow self binding for generic ParamSpec
1 parent 4310586 commit fd4a62a

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,
@@ -792,6 +793,33 @@ def analyze_var(
792793
else:
793794
call_type = typ
794795

796+
if isinstance(call_type, Instance) and any(
797+
isinstance(arg, Parameters) for arg in call_type.args
798+
):
799+
args: list[Type] = []
800+
for arg in call_type.args:
801+
if not isinstance(arg, Parameters):
802+
args.append(arg)
803+
continue
804+
c = callable_type_from_parameters(arg, mx.chk.named_type("builtins.function"))
805+
if not var.is_staticmethod:
806+
functype: FunctionLike = c
807+
dispatched_type = meet.meet_types(mx.original_type, itype)
808+
signature = freshen_all_functions_type_vars(functype)
809+
bound = get_proper_type(expand_self_type(var, signature, mx.original_type))
810+
assert isinstance(bound, FunctionLike)
811+
signature = bound
812+
signature = check_self_arg(
813+
signature, dispatched_type, var.is_classmethod, mx.context, name, mx.msg
814+
)
815+
signature = bind_self(signature, mx.self_type, var.is_classmethod)
816+
expanded_signature = expand_type_by_instance(signature, itype)
817+
freeze_all_type_vars(expanded_signature)
818+
assert isinstance(expanded_signature, CallableType)
819+
arg = update_parameters_from_signature(arg, expanded_signature)
820+
args.append(arg)
821+
call_type = call_type.copy_modified(args=args)
822+
result = call_type
795823
if isinstance(call_type, FunctionLike) and not call_type.is_type_obj():
796824
if mx.is_lvalue:
797825
if var.is_property:
@@ -803,7 +831,7 @@ def analyze_var(
803831
if not var.is_staticmethod:
804832
# Class-level function objects and classmethods become bound methods:
805833
# the former to the instance, the latter to the class.
806-
functype: FunctionLike = call_type
834+
functype = call_type
807835
# Use meet to narrow original_type to the dispatched type.
808836
# For example, assume
809837
# * A.f: Callable[[A1], None] where A1 <: A (maybe A1 == A)
@@ -1061,6 +1089,30 @@ def analyze_class_attribute_access(
10611089
isinstance(node.node, FuncBase) and node.node.is_static
10621090
)
10631091
t = get_proper_type(t)
1092+
if isinstance(t, Instance) and any(isinstance(arg, Parameters) for arg in t.args):
1093+
args: list[Type] = []
1094+
for arg in t.args:
1095+
if not isinstance(arg, Parameters):
1096+
args.append(arg)
1097+
continue
1098+
c: FunctionLike = callable_type_from_parameters(
1099+
arg, mx.chk.named_type("builtins.function")
1100+
)
1101+
if is_classmethod:
1102+
c = check_self_arg(c, mx.self_type, False, mx.context, name, mx.msg)
1103+
res = add_class_tvars(
1104+
c,
1105+
isuper,
1106+
is_classmethod,
1107+
is_staticmethod,
1108+
mx.self_type,
1109+
original_vars=original_vars,
1110+
)
1111+
signature = get_proper_type(res)
1112+
assert isinstance(signature, CallableType)
1113+
arg = update_parameters_from_signature(arg, signature)
1114+
args.append(arg)
1115+
t = t.copy_modified(args=args)
10641116
if isinstance(t, FunctionLike) and is_classmethod:
10651117
t = check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg)
10661118
result = add_class_tvars(
@@ -1348,3 +1400,30 @@ def is_valid_constructor(n: SymbolNode | None) -> bool:
13481400
if isinstance(n, Decorator):
13491401
return isinstance(get_proper_type(n.type), FunctionLike)
13501402
return False
1403+
1404+
1405+
def callable_type_from_parameters(
1406+
param: Parameters, fallback: Instance, ret_type: Type | None = None
1407+
) -> CallableType:
1408+
"""Create CallableType from Parameters."""
1409+
return CallableType(
1410+
arg_types=param.arg_types,
1411+
arg_kinds=param.arg_kinds,
1412+
arg_names=param.arg_names,
1413+
ret_type=ret_type if ret_type is not None else NoneType(),
1414+
fallback=fallback,
1415+
variables=param.variables,
1416+
imprecise_arg_kinds=param.imprecise_arg_kinds,
1417+
)
1418+
1419+
1420+
def update_parameters_from_signature(param: Parameters, signature: CallableType) -> Parameters:
1421+
"""Update Parameters from signature."""
1422+
return param.copy_modified(
1423+
arg_types=signature.arg_types,
1424+
arg_kinds=signature.arg_kinds,
1425+
arg_names=signature.arg_names,
1426+
is_ellipsis_args=signature.is_ellipsis_args,
1427+
variables=signature.variables,
1428+
imprecise_arg_kinds=signature.imprecise_arg_kinds,
1429+
)

test-data/unit/check-selftype.test

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2071,3 +2071,98 @@ p: Partial
20712071
reveal_type(p()) # N: Revealed type is "Never"
20722072
p2: Partial2
20732073
reveal_type(p2(42)) # N: Revealed type is "builtins.int"
2074+
2075+
[case testSelfTypeBindingWithGenericParamSpec]
2076+
from typing import Generic, Callable
2077+
from typing_extensions import ParamSpec
2078+
2079+
P = ParamSpec("P")
2080+
2081+
class Wrapper(Generic[P]):
2082+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None: ...
2083+
2084+
def decorator(f: Callable[P, None]) -> Callable[P, None]: ...
2085+
def lru_cache(f: Callable[P, None]) -> Wrapper[P]: ...
2086+
2087+
class A:
2088+
@decorator
2089+
def method1(self, val: int) -> None: ...
2090+
2091+
@lru_cache
2092+
def method2(self, val: int) -> None: ...
2093+
2094+
def test(self) -> None:
2095+
reveal_type(self.method1) # N: Revealed type is "def (val: builtins.int)"
2096+
reveal_type(self.method2) # N: Revealed type is "__main__.Wrapper[[val: builtins.int]]"
2097+
reveal_type(A.method1) # N: Revealed type is "def (self: __main__.A, val: builtins.int)"
2098+
reveal_type(A.method2) # N: Revealed type is "__main__.Wrapper[[self: __main__.A, val: builtins.int]]"
2099+
2100+
self.method1(2)
2101+
self.method2(2)
2102+
[builtins fixtures/tuple.pyi]
2103+
2104+
[case testSelfTypeBindingWithGenericParamSpecClassmethod]
2105+
from typing import Generic, Callable
2106+
from typing_extensions import ParamSpec
2107+
2108+
P = ParamSpec("P")
2109+
2110+
class Wrapper(Generic[P]):
2111+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None: ...
2112+
2113+
def decorator(f: Callable[P, None]) -> Callable[P, None]: ...
2114+
def lru_cache(f: Callable[P, None]) -> Wrapper[P]: ...
2115+
2116+
class A:
2117+
@classmethod
2118+
@decorator
2119+
def method1(cls, val: int) -> None: ...
2120+
2121+
@classmethod
2122+
@lru_cache
2123+
def method2(cls, val: int) -> None: ...
2124+
2125+
def test(self) -> None:
2126+
reveal_type(self.method1) # N: Revealed type is "def (val: builtins.int)"
2127+
reveal_type(self.method2) # N: Revealed type is "__main__.Wrapper[[val: builtins.int]]"
2128+
reveal_type(A.method1) # N: Revealed type is "def (val: builtins.int)"
2129+
reveal_type(A.method2) # N: Revealed type is "__main__.Wrapper[[val: builtins.int]]"
2130+
2131+
self.method1(2)
2132+
self.method2(2)
2133+
A.method1(2)
2134+
A.method2(2)
2135+
[builtins fixtures/classmethod.pyi]
2136+
2137+
[case testSelfTypeBindingWithGenericParamSpecStaticmethod]
2138+
from typing import Generic, Callable
2139+
from typing_extensions import ParamSpec
2140+
2141+
P = ParamSpec("P")
2142+
2143+
class Wrapper(Generic[P]):
2144+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None: ...
2145+
2146+
def decorator(f: Callable[P, None]) -> Callable[P, None]: ...
2147+
def lru_cache(f: Callable[P, None]) -> Wrapper[P]: ...
2148+
2149+
class A:
2150+
@staticmethod
2151+
@decorator
2152+
def method1(val: int) -> None: ...
2153+
2154+
@staticmethod
2155+
@lru_cache
2156+
def method2(val: int) -> None: ...
2157+
2158+
def test(self) -> None:
2159+
reveal_type(self.method1) # N: Revealed type is "def (val: builtins.int)"
2160+
reveal_type(self.method2) # N: Revealed type is "__main__.Wrapper[[val: builtins.int]]"
2161+
reveal_type(A.method1) # N: Revealed type is "def (val: builtins.int)"
2162+
reveal_type(A.method2) # N: Revealed type is "__main__.Wrapper[[val: builtins.int]]"
2163+
2164+
self.method1(2)
2165+
self.method2(2)
2166+
A.method1(2)
2167+
A.method2(2)
2168+
[builtins fixtures/classmethod.pyi]

0 commit comments

Comments
 (0)