From 4bba57d536c8e8c928df00f72fb2b3496280874b Mon Sep 17 00:00:00 2001 From: dnwpark Date: Wed, 14 Jan 2026 18:38:17 -0800 Subject: [PATCH 1/8] Add qualifier "positional" for params. --- tests/test_type_dir.py | 2 +- typemap/type_eval/_eval_operators.py | 35 +++++++++++++--------------- typemap/typing.py | 2 +- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/tests/test_type_dir.py b/tests/test_type_dir.py index bf4e32d..71387db 100644 --- a/tests/test_type_dir.py +++ b/tests/test_type_dir.py @@ -218,7 +218,7 @@ def test_type_dir_1b(): assert format_helper.format_class(d) == textwrap.dedent("""\ class CMethod: @classmethod - def cbase2(_arg0: type[tests.test_type_dir.CMethod], _arg1: int, /, a: bool | None) -> int: ... + def cbase2(cls: type[tests.test_type_dir.CMethod], lol: int, /, a: bool | None) -> int: ... """) diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 0694821..3489846 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -335,7 +335,7 @@ def _callable_type_to_signature(callable_type: object) -> inspect.Signature: elif "keyword" in quals: kind = inspect.Parameter.KEYWORD_ONLY saw_keyword_only = True - elif name is None: + elif "positional" in quals or name is None: kind = inspect.Parameter.POSITIONAL_ONLY elif saw_keyword_only: kind = inspect.Parameter.KEYWORD_ONLY @@ -395,8 +395,10 @@ def fn(*args, **kwargs): def _is_pos_only(param): name, _, quals = typing.get_args(param) - name = _from_literal(name) - return name is None and not (_get_quals(quals) & {"*", "**"}) + qual_set = _get_quals(quals) + return "positional" in qual_set or ( + name is None and not (_get_quals(quals) & {"*", "**"}) + ) def _callable_type_to_method(name, typ): @@ -419,16 +421,9 @@ def _callable_type_to_method(name, typ): cls, params, ret = typing.get_args(typ) # We have to make class positional only if there is some other # positional only argument. Annoying! - pname = ( - "cls" - if not any(_is_pos_only(p) for p in typing.get_args(params)) - else None - ) - cls_param = Param[ - typing.Literal[pname], - type[cls], - typing.Never, - ] + has_pos_only = any(_is_pos_only(p) for p in typing.get_args(params)) + quals = typing.Literal["positional"] if has_pos_only else typing.Never + cls_param = Param[typing.Literal["cls"], type[cls], quals] typ = typing.Callable[[cls_param] + list(typing.get_args(params)), ret] elif head is staticmethod: params, ret = typing.get_args(typ) @@ -485,10 +480,6 @@ def _ann(x): else: specified_receiver = ann - has_name = p.kind in ( - inspect.Parameter.POSITIONAL_OR_KEYWORD, - inspect.Parameter.KEYWORD_ONLY, - ) quals = [] if p.kind == inspect.Parameter.VAR_POSITIONAL: quals.append("*") @@ -496,11 +487,13 @@ def _ann(x): quals.append("**") if p.kind == inspect.Parameter.KEYWORD_ONLY: quals.append("keyword") + if p.kind == inspect.Parameter.POSITIONAL_ONLY: + quals.append("positional") if p.default is not empty: quals.append("default") params.append( Param[ - typing.Literal[p.name if has_name else None], + typing.Literal[p.name], _ann(ann), typing.Literal[*quals] if quals else typing.Never, ] @@ -594,7 +587,11 @@ def _fix_callable_args(base, args): if typing.get_origin(special) is tuple: args[idx] = tuple[ *[ - t if isinstance(t, Param) else Param[typing.Literal[None], t] + ( + t + if typing.get_origin(t) is Param + else Param[typing.Literal[None], t] + ) for t in typing.get_args(special) ] ] diff --git a/typemap/typing.py b/typemap/typing.py index e7e7dd8..e955610 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -92,7 +92,7 @@ class Member[ definer: D -ParamQuals = Literal["*", "**", "keyword", "default"] +ParamQuals = Literal["*", "**", "keyword", "positional", "default"] class Param[N: str | None, T, Q: ParamQuals = typing.Never]: From 02f487165d636724443886e0a3332a24e9407c37 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Wed, 14 Jan 2026 18:44:16 -0800 Subject: [PATCH 2/8] Add tests for getting callables out of classes. --- tests/test_type_eval.py | 147 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 9f16d38..8558163 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -9,6 +9,7 @@ List, Literal, Never, + Self, Tuple, TypeVar, Union, @@ -24,12 +25,14 @@ GetArgs, GetAttr, GetName, + GetQuals, GetType, GetAnnotations, IsSub, Iter, Length, Member, + Members, NewProtocol, Param, SpecialFormEllipsis, @@ -688,6 +691,150 @@ class Container2[T]: ... assert eval_typing(GetArg[t, Container, 1]) == Never +def test_eval_getarg_callable_01(): + f = Callable[[int], int] + t = eval_typing(GetArg[f, Callable, 0]) + assert t == tuple[Param[Literal[None], int, Never]] + t = eval_typing(GetArg[f, Callable, 1]) + assert t is int + + +type IndirectProtocol[T] = NewProtocol[ + *[ + Member[GetName[m], GetType[m], GetQuals[m]] + for m in Iter[Members[T]] + if not Sub[GetType[m], Callable] + or not Sub[Literal["ClassVar"], GetQuals[m]] + ], + *[ + ( + Member[ + GetName[m], + Callable[ + [ + *[ + Param[Literal["self"], Self, GetQuals[p]] + for p in Iter[GetArg[GetType[m], Callable, 0]] + if Sub[Literal["self"], GetName[p]] + ], + *[ + Param[GetName[p], GetType[p], GetQuals[p]] + for p in Iter[GetArg[GetType[m], Callable, 0]] + if not Sub[Literal["self"], GetName[p]] + ], + ], + GetArg[GetType[m], Callable, 1], + ], + GetQuals[m], + ] + ) + for m in Iter[Members[T]] + if Sub[GetType[m], Callable] and Sub[Literal["ClassVar"], GetQuals[m]] + ], +] + +type GetCallableNamed[T, Name] = GetArg[ + tuple[ + *[ + GetType[p] + for p in Iter[Members[T]] + if Sub[GetType[p], Callable] and Sub[Name, GetName[p]] + ], + ], + tuple, + 0, +] + + +def test_eval_getarg_callable_02(): + class C: + def f(self, x: int, /, y: int, *, z: int) -> int: ... + + f = eval_typing(GetCallableNamed[IndirectProtocol[C], Literal["f"]]) + t = eval_typing(GetArg[f, Callable, 0]) + assert ( + t + == tuple[ + Param[Literal["self"], Self, Literal["positional"]], + Param[Literal["x"], int, Literal["positional"]], + Param[Literal["y"], int], + Param[Literal["z"], int, Literal["keyword"]], + ] + ) + t = eval_typing(GetArg[f, Callable, 1]) + assert t is int + + +def test_eval_getarg_callable_03(): + class C: + @classmethod + def f(cls, x: int, /, y: int, *, z: int) -> int: ... + + t = eval_typing(GetCallableNamed[IndirectProtocol[C], Literal["f"]]) + assert ( + t + == Callable[ + [ + Param[Literal["cls"], type[C], Literal["positional"]], + Param[Literal["x"], int, Literal["positional"]], + Param[Literal["y"], int], + Param[Literal["z"], int, Literal["keyword"]], + ], + int, + ] + ) + + +def test_eval_getarg_callable_04(): + class C: + @classmethod + def f(cls, x: int, /, y: int, *, z: int) -> int: ... + + f = eval_typing(GetCallableNamed[IndirectProtocol[C], Literal["f"]]) + t = eval_typing(GetArg[f, Callable, 0]) + assert ( + t + == tuple[ + Param[Literal["cls"], type[C], Literal["positional"]], + Param[Literal["x"], int, Literal["positional"]], + Param[Literal["y"], int], + Param[Literal["z"], int, Literal["keyword"]], + ] + ) + t = eval_typing(GetArg[f, Callable, 1]) + assert t is int + + +def test_eval_getarg_callable_05(): + class C: + @staticmethod + def f(x: int, /, y: int, *, z: int) -> int: ... + + f = eval_typing(GetCallableNamed[IndirectProtocol[C], Literal["f"]]) + t = eval_typing(GetArg[f, Callable, 0]) + assert ( + t + == tuple[ + Param[Literal["x"], int, Literal["positional"]], + Param[Literal["y"], int], + Param[Literal["z"], int, Literal["keyword"]], + ] + ) + t = eval_typing(GetArg[f, Callable, 1]) + assert t is int + + +def test_eval_getarg_callable_06(): + class C: + f: Callable[[int], int] + + f = eval_typing(GetCallableNamed[IndirectProtocol[C], Literal["f"]]) + t = eval_typing(GetArg[f, Callable, 0]) + assert t == tuple[Param[Literal[None], int, Never],] + t = eval_typing(GetArg[f, Callable, 1]) + assert t is int + + type _Works[Ts, I] = Literal[True] type Works[Ts] = _Works[Ts, Length[Ts]] From 2df047c5cf8c1881096dd7ced9fde1efb1faba79 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Wed, 14 Jan 2026 18:53:20 -0800 Subject: [PATCH 3/8] Fix getting args from staticmethods. --- typemap/type_eval/_eval_operators.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 3489846..d4d4d4e 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -612,6 +612,9 @@ def _get_raw_args(tp, base_head, ctx) -> typing.Any: return args + if tp_head is staticmethod: + return typing.get_args(evaled) + # Scan the fully-annotated MRO to find the base box = _apply_generic.box(tp) for anc in box.mro: From fd52205c4f2adae8c3d26df1f87881096bea3aa6 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Fri, 16 Jan 2026 14:45:09 -0800 Subject: [PATCH 4/8] Update PosParam and PosDefaultParam. --- spec-draft.rst | 8 ++++---- tests/test_type_eval.py | 32 ++++++++++++++++++++++++++++++++ typemap/typing.py | 6 ++++-- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/spec-draft.rst b/spec-draft.rst index 653408e..3505dde 100644 --- a/spec-draft.rst +++ b/spec-draft.rst @@ -29,8 +29,8 @@ We introduce a ``Param`` type the contains all the information about a function ParamQuals = typing.Literal["*", "**", "default", "keyword"] - type PosParam[T] = Param[Literal[None], T] - type PosDefaultParam[T] = Param[Literal[None], T, Literal["default"]] + type PosParam[N: str | None, T] = Param[N, T, Literal["positional"]] + type PosDefaultParam[N: str | None, T] = Param[N, T, Literal["positional", "default"]] type DefaultParam[N: str, T] = Param[N, T, Literal["default"]] type NamedParam[N: str, T] = Param[N, T, Literal["keyword"]] type NamedDefaultParam[N: str, T] = Param[N, T, Literal["keyword", "default"]] @@ -55,7 +55,7 @@ as (we are omiting the ``Literal`` in places):: Callable[ [ - Param[None, int], + Param["a", int, "positional"], Param["b", int], Param["c", int, "default"], Param[None, int, "*"], @@ -71,7 +71,7 @@ or, using the type abbreviations we provide:: Callable[ [ - PosParam[int], + PosParam["a", int], Param["b", int], DefaultParam["c", int, ArgsParam[int, "*"], diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 8558163..6d7d5d0 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -140,6 +140,38 @@ class F[bool]: type NestedTree = str | list[NestedTree] | list[IntTree] +def test_eval_types_4(): + d = eval_typing( + Callable[ + [ + Param[Literal["a"], int, Literal["positional"]], + Param[Literal["b"], int], + Param[Literal["c"], int, Literal["default"]], + Param[None, int, Literal["*"]], + Param[Literal["d"], int, Literal["keyword"]], + Param[Literal["e"], int, Literal["default", "keyword"]], + Param[None, int, Literal["**"]], + ], + int, + ] + ) + assert ( + d + == Callable[ + [ + Param[Literal["a"], int, Literal["positional"]], + Param[Literal["b"], int], + Param[Literal["c"], int, Literal["default"]], + Param[None, int, Literal["*"]], + Param[Literal["d"], int, Literal["keyword"]], + Param[Literal["e"], int, Literal["default", "keyword"]], + Param[None, int, Literal["**"]], + ], + int, + ] + ) + + class TA: x: int y: list[float] diff --git a/typemap/typing.py b/typemap/typing.py index e955610..021483d 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -101,8 +101,10 @@ class Param[N: str | None, T, Q: ParamQuals = typing.Never]: quals: Q -type PosParam[T] = Param[Literal[None], T] -type PosDefaultParam[T] = Param[Literal[None], T, Literal["default"]] +type PosParam[N: str | None, T] = Param[N, T, Literal["positional"]] +type PosDefaultParam[N: str | None, T] = Param[ + N, T, Literal["positional", "default"] +] type DefaultParam[N: str, T] = Param[N, T, Literal["default"]] type NamedParam[N: str, T] = Param[N, T, Literal["keyword"]] type NamedDefaultParam[N: str, T] = Param[N, T, Literal["keyword", "default"]] From 9bf5f9659d7e9a2f87e3125e76be83b5182bd244 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Fri, 16 Jan 2026 17:27:41 -0800 Subject: [PATCH 5/8] Remove weird code. --- typemap/type_eval/_eval_operators.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index d4d4d4e..3489846 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -612,9 +612,6 @@ def _get_raw_args(tp, base_head, ctx) -> typing.Any: return args - if tp_head is staticmethod: - return typing.get_args(evaled) - # Scan the fully-annotated MRO to find the base box = _apply_generic.box(tp) for anc in box.mro: From 8cd5bc45e4e9da47185573cc9a26ac344e2736ea Mon Sep 17 00:00:00 2001 From: dnwpark Date: Fri, 16 Jan 2026 17:27:58 -0800 Subject: [PATCH 6/8] Simplify tests. --- tests/test_type_eval.py | 82 +++++++++-------------------------------- 1 file changed, 18 insertions(+), 64 deletions(-) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 6d7d5d0..89872a7 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -25,7 +25,6 @@ GetArgs, GetAttr, GetName, - GetQuals, GetType, GetAnnotations, IsSub, @@ -731,46 +730,18 @@ def test_eval_getarg_callable_01(): assert t is int -type IndirectProtocol[T] = NewProtocol[ - *[ - Member[GetName[m], GetType[m], GetQuals[m]] - for m in Iter[Members[T]] - if not Sub[GetType[m], Callable] - or not Sub[Literal["ClassVar"], GetQuals[m]] - ], - *[ - ( - Member[ - GetName[m], - Callable[ - [ - *[ - Param[Literal["self"], Self, GetQuals[p]] - for p in Iter[GetArg[GetType[m], Callable, 0]] - if Sub[Literal["self"], GetName[p]] - ], - *[ - Param[GetName[p], GetType[p], GetQuals[p]] - for p in Iter[GetArg[GetType[m], Callable, 0]] - if not Sub[Literal["self"], GetName[p]] - ], - ], - GetArg[GetType[m], Callable, 1], - ], - GetQuals[m], - ] - ) - for m in Iter[Members[T]] - if Sub[GetType[m], Callable] and Sub[Literal["ClassVar"], GetQuals[m]] - ], -] - -type GetCallableNamed[T, Name] = GetArg[ +type IndirectProtocol[T] = NewProtocol[*[m for m in Iter[Members[T]]],] +type GetMethodLike[T, Name] = GetArg[ tuple[ *[ GetType[p] for p in Iter[Members[T]] - if Sub[GetType[p], Callable] and Sub[Name, GetName[p]] + if ( + Sub[GetType[p], Callable] + or Sub[GetType[p], staticmethod] + or Sub[GetType[p], classmethod] + ) + and Sub[Name, GetName[p]] ], ], tuple, @@ -782,7 +753,7 @@ def test_eval_getarg_callable_02(): class C: def f(self, x: int, /, y: int, *, z: int) -> int: ... - f = eval_typing(GetCallableNamed[IndirectProtocol[C], Literal["f"]]) + f = eval_typing(GetMethodLike[IndirectProtocol[C], Literal["f"]]) t = eval_typing(GetArg[f, Callable, 0]) assert ( t @@ -797,32 +768,17 @@ def f(self, x: int, /, y: int, *, z: int) -> int: ... assert t is int -def test_eval_getarg_callable_03(): - class C: - @classmethod - def f(cls, x: int, /, y: int, *, z: int) -> int: ... - - t = eval_typing(GetCallableNamed[IndirectProtocol[C], Literal["f"]]) - assert ( - t - == Callable[ - [ - Param[Literal["cls"], type[C], Literal["positional"]], - Param[Literal["x"], int, Literal["positional"]], - Param[Literal["y"], int], - Param[Literal["z"], int, Literal["keyword"]], - ], - int, - ] - ) +type MembersAreCallable[T] = NewProtocol[ + *[m for m in Iter[Members[T]] if Sub[GetType[m], classmethod]] +] -def test_eval_getarg_callable_04(): +def test_eval_getarg_callable_03(): class C: @classmethod def f(cls, x: int, /, y: int, *, z: int) -> int: ... - f = eval_typing(GetCallableNamed[IndirectProtocol[C], Literal["f"]]) + f = eval_typing(GetMethodLike[IndirectProtocol[C], Literal["f"]]) t = eval_typing(GetArg[f, Callable, 0]) assert ( t @@ -833,16 +789,14 @@ def f(cls, x: int, /, y: int, *, z: int) -> int: ... Param[Literal["z"], int, Literal["keyword"]], ] ) - t = eval_typing(GetArg[f, Callable, 1]) - assert t is int -def test_eval_getarg_callable_05(): +def test_eval_getarg_callable_04(): class C: @staticmethod def f(x: int, /, y: int, *, z: int) -> int: ... - f = eval_typing(GetCallableNamed[IndirectProtocol[C], Literal["f"]]) + f = eval_typing(GetMethodLike[IndirectProtocol[C], Literal["f"]]) t = eval_typing(GetArg[f, Callable, 0]) assert ( t @@ -856,11 +810,11 @@ def f(x: int, /, y: int, *, z: int) -> int: ... assert t is int -def test_eval_getarg_callable_06(): +def test_eval_getarg_callable_05(): class C: f: Callable[[int], int] - f = eval_typing(GetCallableNamed[IndirectProtocol[C], Literal["f"]]) + f = eval_typing(GetMethodLike[IndirectProtocol[C], Literal["f"]]) t = eval_typing(GetArg[f, Callable, 0]) assert t == tuple[Param[Literal[None], int, Never],] t = eval_typing(GetArg[f, Callable, 1]) From e368e57435a0e379e2bf0ccd75bcfe652e5c9e56 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Fri, 16 Jan 2026 17:42:38 -0800 Subject: [PATCH 7/8] Consolidate eval_getarg_callable tests. --- tests/test_type_eval.py | 248 ++++++++++++++++++++++++---------------- 1 file changed, 148 insertions(+), 100 deletions(-) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 89872a7..085fb5d 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -415,7 +415,7 @@ def test_eval_getarg_callable_old(): assert args == Any -def test_eval_getarg_callable(): +def test_eval_getarg_callable_01(): t = Callable[[int, str], str] args = eval_typing(GetArg[t, Callable, 0]) assert ( @@ -447,6 +447,153 @@ def test_eval_getarg_callable(): assert args == Any +type IndirectProtocol[T] = NewProtocol[*[m for m in Iter[Members[T]]],] +type GetMethodLike[T, Name] = GetArg[ + tuple[ + *[ + GetType[p] + for p in Iter[Members[T]] + if ( + Sub[GetType[p], Callable] + or Sub[GetType[p], staticmethod] + or Sub[GetType[p], classmethod] + ) + and Sub[Name, GetName[p]] + ], + ], + tuple, + 0, +] + + +def test_eval_getarg_callable_02a(): + class C: + def f(self, x: int, /, y: int, *, z: int) -> int: ... + + f = eval_typing(GetMethodLike[C, Literal["f"]]) + t = eval_typing(GetArg[f, Callable, 0]) + assert ( + t + == tuple[ + Param[Literal["self"], C, Literal["positional"]], + Param[Literal["x"], int, Literal["positional"]], + Param[Literal["y"], int], + Param[Literal["z"], int, Literal["keyword"]], + ] + ) + t = eval_typing(GetArg[f, Callable, 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, 0]) + assert ( + t + == tuple[ + Param[Literal["self"], Self, Literal["positional"]], + Param[Literal["x"], int, Literal["positional"]], + Param[Literal["y"], int], + Param[Literal["z"], int, Literal["keyword"]], + ] + ) + t = eval_typing(GetArg[f, Callable, 1]) + assert t is int + + +def test_eval_getarg_callable_03a(): + class C: + @classmethod + def f(cls, x: int, /, y: int, *, z: int) -> int: ... + + f = eval_typing(GetMethodLike[C, Literal["f"]]) + t = eval_typing(GetArg[f, classmethod, 0]) + assert t == C + t = eval_typing(GetArg[f, classmethod, 1]) + assert ( + t + == tuple[ + Param[Literal["x"], int, Literal["positional"]], + Param[Literal["y"], int], + Param[Literal["z"], int, Literal["keyword"]], + ] + ) + t = eval_typing(GetArg[f, classmethod, 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, Callable, 0]) + assert ( + t + == tuple[ + Param[Literal["cls"], type[C], Literal["positional"]], + Param[Literal["x"], int, Literal["positional"]], + Param[Literal["y"], int], + Param[Literal["z"], int, Literal["keyword"]], + ] + ) + t = eval_typing(GetArg[f, Callable, 1]) + assert t is int + + +def test_eval_getarg_callable_04a(): + class C: + @staticmethod + def f(x: int, /, y: int, *, z: int) -> int: ... + + f = eval_typing(GetMethodLike[C, Literal["f"]]) + t = eval_typing(GetArg[f, staticmethod, 0]) + assert ( + t + == tuple[ + Param[Literal["x"], int, Literal["positional"]], + Param[Literal["y"], int], + Param[Literal["z"], int, Literal["keyword"]], + ] + ) + t = eval_typing(GetArg[f, staticmethod, 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, Callable, 0]) + assert ( + t + == tuple[ + Param[Literal["x"], int, Literal["positional"]], + Param[Literal["y"], int], + Param[Literal["z"], int, Literal["keyword"]], + ] + ) + t = eval_typing(GetArg[f, Callable, 1]) + assert t is int + + +def test_eval_getarg_callable_05(): + class C: + f: Callable[[int], int] + + f = eval_typing(GetMethodLike[IndirectProtocol[C], Literal["f"]]) + t = eval_typing(GetArg[f, Callable, 0]) + assert t == tuple[Param[Literal[None], int, Never],] + t = eval_typing(GetArg[f, Callable, 1]) + assert t is int + + def test_eval_getarg_tuple(): t = tuple[int, ...] args = eval_typing(GetArg[t, tuple, 1]) @@ -722,105 +869,6 @@ class Container2[T]: ... assert eval_typing(GetArg[t, Container, 1]) == Never -def test_eval_getarg_callable_01(): - f = Callable[[int], int] - t = eval_typing(GetArg[f, Callable, 0]) - assert t == tuple[Param[Literal[None], int, Never]] - t = eval_typing(GetArg[f, Callable, 1]) - assert t is int - - -type IndirectProtocol[T] = NewProtocol[*[m for m in Iter[Members[T]]],] -type GetMethodLike[T, Name] = GetArg[ - tuple[ - *[ - GetType[p] - for p in Iter[Members[T]] - if ( - Sub[GetType[p], Callable] - or Sub[GetType[p], staticmethod] - or Sub[GetType[p], classmethod] - ) - and Sub[Name, GetName[p]] - ], - ], - tuple, - 0, -] - - -def test_eval_getarg_callable_02(): - 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, 0]) - assert ( - t - == tuple[ - Param[Literal["self"], Self, Literal["positional"]], - Param[Literal["x"], int, Literal["positional"]], - Param[Literal["y"], int], - Param[Literal["z"], int, Literal["keyword"]], - ] - ) - t = eval_typing(GetArg[f, Callable, 1]) - assert t is int - - -type MembersAreCallable[T] = NewProtocol[ - *[m for m in Iter[Members[T]] if Sub[GetType[m], classmethod]] -] - - -def test_eval_getarg_callable_03(): - 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, Callable, 0]) - assert ( - t - == tuple[ - Param[Literal["cls"], type[C], Literal["positional"]], - Param[Literal["x"], int, Literal["positional"]], - Param[Literal["y"], int], - Param[Literal["z"], int, Literal["keyword"]], - ] - ) - - -def test_eval_getarg_callable_04(): - 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, Callable, 0]) - assert ( - t - == tuple[ - Param[Literal["x"], int, Literal["positional"]], - Param[Literal["y"], int], - Param[Literal["z"], int, Literal["keyword"]], - ] - ) - t = eval_typing(GetArg[f, Callable, 1]) - assert t is int - - -def test_eval_getarg_callable_05(): - class C: - f: Callable[[int], int] - - f = eval_typing(GetMethodLike[IndirectProtocol[C], Literal["f"]]) - t = eval_typing(GetArg[f, Callable, 0]) - assert t == tuple[Param[Literal[None], int, Never],] - t = eval_typing(GetArg[f, Callable, 1]) - assert t is int - - type _Works[Ts, I] = Literal[True] type Works[Ts] = _Works[Ts, Length[Ts]] From d4c4ef455fc9323f12b7acb618bffc258f4ae362 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Fri, 16 Jan 2026 17:45:31 -0800 Subject: [PATCH 8/8] Sub -> IsSub --- tests/test_type_eval.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 085fb5d..0140e27 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -454,11 +454,11 @@ def test_eval_getarg_callable_01(): GetType[p] for p in Iter[Members[T]] if ( - Sub[GetType[p], Callable] - or Sub[GetType[p], staticmethod] - or Sub[GetType[p], classmethod] + IsSub[GetType[p], Callable] + or IsSub[GetType[p], staticmethod] + or IsSub[GetType[p], classmethod] ) - and Sub[Name, GetName[p]] + and IsSub[Name, GetName[p]] ], ], tuple,