From e9bf6470c8e4af24331a0d7ca5b6fadc7c2046e6 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Tue, 27 Jan 2026 09:51:35 -0800 Subject: [PATCH 1/2] Add tests. --- tests/test_type_eval.py | 140 ++++++++++++++++++++++++++++++++++------ 1 file changed, 119 insertions(+), 21 deletions(-) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 30beb31..9eee8ca 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -21,6 +21,7 @@ from typemap.typing import ( Attrs, FromUnion, + GenericCallable, GetArg, GetArgs, GetAttr, @@ -451,6 +452,41 @@ def test_eval_getarg_callable_01(): assert args == Any +def test_eval_getarg_callable_02(): + # GenericCallable + T = TypeVar("T") + + # Params not wrapped + f = Callable[[T], T] + gc = GenericCallable[tuple[T], f] + t = eval_typing(GetArg[gc, GenericCallable, Literal[0]]) + assert t == tuple[T] + gc_f = eval_typing(GetArg[gc, GenericCallable, Literal[1]]) + assert gc_f == f + t = eval_typing(GetArg[gc_f, Callable, Literal[0]]) + assert t == tuple[Param[Literal[None], T, Never]] + t = eval_typing(GetArg[gc_f, Callable, Literal[1]]) + assert t is T + + # Params wrapped + f = Callable[ + [ + Param[Literal[None], T, Literal["positional"]], + Param[Literal["y"], T], + Param[Literal["z"], T, Literal["keyword"]], + ], + T, + ] + gc = GenericCallable[ + tuple[T], + f, + ] + t = eval_typing(GetArg[gc, GenericCallable, Literal[0]]) + assert t == tuple[T] + gc_f = eval_typing(GetArg[gc, GenericCallable, Literal[1]]) + assert gc_f == f + + type IndirectProtocol[T] = NewProtocol[*[m for m in Iter[Members[T]]],] type GetMethodLike[T, Name] = GetArg[ tuple[ @@ -461,6 +497,7 @@ def test_eval_getarg_callable_01(): IsSub[GetType[p], Callable] or IsSub[GetType[p], staticmethod] or IsSub[GetType[p], classmethod] + or IsSub[GetType[p], GenericCallable] ) and IsSub[Name, GetName[p]] ], @@ -470,7 +507,8 @@ def test_eval_getarg_callable_01(): ] -def test_eval_getarg_callable_02a(): +def test_eval_getarg_callable_03(): + # member function class C: def f(self, x: int, /, y: int, *, z: int) -> int: ... @@ -488,11 +526,6 @@ def f(self, x: int, /, y: int, *, z: int) -> int: ... t = eval_typing(GetArg[f, Callable, Literal[1]]) assert t is int - -def test_eval_getarg_callable_02b(): - class C: - def f(self, x: int, /, y: int, *, z: int) -> int: ... - f = eval_typing(GetMethodLike[IndirectProtocol[C], Literal["f"]]) t = eval_typing(GetArg[f, Callable, Literal[0]]) assert ( @@ -508,7 +541,8 @@ def f(self, x: int, /, y: int, *, z: int) -> int: ... assert t is int -def test_eval_getarg_callable_03a(): +def test_eval_getarg_callable_04(): + # classmethod class C: @classmethod def f(cls, x: int, /, y: int, *, z: int) -> int: ... @@ -528,12 +562,6 @@ def f(cls, x: int, /, y: int, *, z: int) -> int: ... t = eval_typing(GetArg[f, classmethod, Literal[2]]) assert t is int - -def test_eval_getarg_callable_03b(): - class C: - @classmethod - def f(cls, x: int, /, y: int, *, z: int) -> int: ... - f = eval_typing(GetMethodLike[IndirectProtocol[C], Literal["f"]]) t = eval_typing(GetArg[f, classmethod, Literal[0]]) t = eval_typing(GetArg[f, classmethod, Literal[1]]) @@ -549,7 +577,8 @@ def f(cls, x: int, /, y: int, *, z: int) -> int: ... assert t is int -def test_eval_getarg_callable_04a(): +def test_eval_getarg_callable_05(): + # staticmethod class C: @staticmethod def f(x: int, /, y: int, *, z: int) -> int: ... @@ -567,12 +596,6 @@ def f(x: int, /, y: int, *, z: int) -> int: ... t = eval_typing(GetArg[f, staticmethod, Literal[1]]) assert t is int - -def test_eval_getarg_callable_04b(): - class C: - @staticmethod - def f(x: int, /, y: int, *, z: int) -> int: ... - f = eval_typing(GetMethodLike[IndirectProtocol[C], Literal["f"]]) t = eval_typing(GetArg[f, staticmethod, Literal[0]]) assert ( @@ -587,7 +610,8 @@ def f(x: int, /, y: int, *, z: int) -> int: ... assert t is int -def test_eval_getarg_callable_05(): +def test_eval_getarg_callable_06(): + # member callable attr class C: f: Callable[[int], int] @@ -598,6 +622,80 @@ class C: assert t is int +def test_eval_getarg_callable_07(): + # generic member function + class C: + def f[T](self, x: T, /, y: T, *, z: T) -> T: ... + + gc = eval_typing(GetMethodLike[C, Literal["f"]]) + _T = eval_typing( + GetArg[GetArg[gc, GenericCallable, Literal[0]], tuple, Literal[0]] + ) + f = eval_typing(GetArg[gc, GenericCallable, Literal[1]]) + t = eval_typing(GetArg[f, Callable, Literal[0]]) + assert ( + t + == tuple[ + Param[Literal["self"], C, Literal["positional"]], + Param[Literal["x"], _T, Literal["positional"]], + Param[Literal["y"], _T], + Param[Literal["z"], _T, Literal["keyword"]], + ] + ) + t = eval_typing(GetArg[f, Callable, Literal[1]]) + assert t is _T + + +def test_eval_getarg_callable_08(): + # generic classmethod + class C: + @classmethod + def f[T](cls, x: T, /, y: T, *, z: T) -> T: ... + + gc = eval_typing(GetMethodLike[C, Literal["f"]]) + _T = eval_typing( + GetArg[GetArg[gc, GenericCallable, Literal[0]], tuple, Literal[0]] + ) + f = eval_typing(GetArg[gc, GenericCallable, Literal[1]]) + t = eval_typing(GetArg[f, classmethod, Literal[0]]) + assert t is C + t = eval_typing(GetArg[f, classmethod, Literal[1]]) + assert ( + t + == tuple[ + Param[Literal["x"], _T, Literal["positional"]], + Param[Literal["y"], _T], + Param[Literal["z"], _T, Literal["keyword"]], + ] + ) + t = eval_typing(GetArg[f, classmethod, Literal[2]]) + assert t is _T + + +def test_eval_getarg_callable_09(): + # generic staticmethod + class C: + @staticmethod + def f[T](x: T, /, y: T, *, z: T) -> T: ... + + gc = eval_typing(GetMethodLike[C, Literal["f"]]) + _T = eval_typing( + GetArg[GetArg[gc, GenericCallable, Literal[0]], tuple, Literal[0]] + ) + f = eval_typing(GetArg[gc, GenericCallable, Literal[1]]) + t = eval_typing(GetArg[f, staticmethod, Literal[0]]) + assert ( + t + == tuple[ + Param[Literal["x"], _T, Literal["positional"]], + Param[Literal["y"], _T], + Param[Literal["z"], _T, Literal["keyword"]], + ] + ) + t = eval_typing(GetArg[f, staticmethod, Literal[1]]) + assert t is _T + + def test_eval_getarg_tuple(): t = tuple[int, ...] args = eval_typing(GetArg[t, tuple, Literal[1]]) From 5d81bee7a8a126832d0e293a62e889375823d898 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Tue, 27 Jan 2026 12:56:14 -0800 Subject: [PATCH 2/2] Prevent GenericCallable type vars from being wrapped in Param. --- typemap/type_eval/_eval_operators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 53823cc..49a60ab 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -704,7 +704,7 @@ def _get_raw_args(tp, base_head, ctx) -> typing.Any: if tp_head is base_head: args = typing.get_args(evaled) - if _is_method_like(tp): + if _is_method_like(tp) and base_head is not GenericCallable: args = _fix_callable_args(base_head, args) return args