diff --git a/tests/test_dataclass_like.py b/tests/test_dataclass_like.py index 3582aa8..ee6b7c6 100644 --- a/tests/test_dataclass_like.py +++ b/tests/test_dataclass_like.py @@ -53,7 +53,7 @@ class Field[T: FieldArgs](typing.InitField[T]): type InitFnType[T] = typing.Member[ Literal["__init__"], Callable[ - [ + typing.Params[ typing.Param[Literal["self"], Self], *[ typing.Param[ diff --git a/tests/test_eval_call_with_types.py b/tests/test_eval_call_with_types.py index e2e3e4e..8419e1a 100644 --- a/tests/test_eval_call_with_types.py +++ b/tests/test_eval_call_with_types.py @@ -10,6 +10,7 @@ Iter, Members, Param, + Params, ) @@ -19,13 +20,16 @@ def test_eval_call_with_types_callable_01(): def test_eval_call_with_types_callable_02(): - res = eval_call_with_types(Callable[[Param[Literal["x"], int]], int], int) + res = eval_call_with_types( + Callable[Params[Param[Literal["x"], int]], int], int + ) assert res is int def test_eval_call_with_types_callable_03(): res = eval_call_with_types( - Callable[[Param[Literal["x"], int, Literal["keyword"]]], int], x=int + Callable[Params[Param[Literal["x"], int, Literal["keyword"]]], int], + x=int, ) assert res is int @@ -33,14 +37,18 @@ def test_eval_call_with_types_callable_03(): def test_eval_call_with_types_callable_04(): class C: ... - res = eval_call_with_types(Callable[[Param[Literal["self"], Self]], int], C) + res = eval_call_with_types( + Callable[Params[Param[Literal["self"], Self]], int], C + ) assert res is int def test_eval_call_with_types_callable_05(): class C: ... - res = eval_call_with_types(Callable[[Param[Literal["self"], Self]], C], C) + res = eval_call_with_types( + Callable[Params[Param[Literal["self"], Self]], C], C + ) assert res is C @@ -48,7 +56,10 @@ def test_eval_call_with_types_callable_06(): class C: ... res = eval_call_with_types( - Callable[[Param[Literal["self"], Self], Param[Literal["x"], int]], int], + Callable[ + Params[Param[Literal["self"], Self], Param[Literal["x"], int]], + int, + ], C, int, ) @@ -60,7 +71,7 @@ class C: ... res = eval_call_with_types( Callable[ - [ + Params[ Param[Literal["self"], Self], Param[Literal["x"], int, Literal["keyword"]], ], @@ -74,13 +85,15 @@ class C: ... def test_eval_call_with_types_callable_08(): T = TypeVar("T") - res = eval_call_with_types(Callable[[Param[Literal["x"], T]], str], int) + res = eval_call_with_types( + Callable[Params[Param[Literal["x"], T]], str], int + ) assert res is str def test_eval_call_with_types_callable_09(): T = TypeVar("T") - res = eval_call_with_types(Callable[[Param[Literal["x"], T]], T], int) + res = eval_call_with_types(Callable[Params[Param[Literal["x"], T]], T], int) assert res is int @@ -89,7 +102,9 @@ def test_eval_call_with_types_callable_10(): class C(Generic[T]): ... - res = eval_call_with_types(Callable[[Param[Literal["x"], C[T]]], T], C[int]) + res = eval_call_with_types( + Callable[Params[Param[Literal["x"], C[T]]], T], C[int] + ) assert res is int @@ -102,9 +117,13 @@ class D(C[int]): ... class E(D): ... - res = eval_call_with_types(Callable[[Param[Literal["x"], C[T]]], T], D) + res = eval_call_with_types( + Callable[Params[Param[Literal["x"], C[T]]], T], D + ) assert res is int - res = eval_call_with_types(Callable[[Param[Literal["x"], C[T]]], T], E) + res = eval_call_with_types( + Callable[Params[Param[Literal["x"], C[T]]], T], E + ) assert res is int @@ -206,7 +225,10 @@ def test_eval_call_with_types_bind_error_01(): ValueError, match="Type variable T is already bound to int, but got str" ): eval_call_with_types( - Callable[[Param[Literal["x"], T], Param[Literal["y"], T]], T], + Callable[ + Params[Param[Literal["x"], T], Param[Literal["y"], T]], + T, + ], int, str, ) @@ -230,7 +252,10 @@ class C(Generic[T]): ... ValueError, match="Type variable T is already bound to int, but got str" ): eval_call_with_types( - Callable[[Param[Literal["x"], C[T]], Param[Literal["y"], C[T]]], T], + Callable[ + Params[Param[Literal["x"], C[T]], Param[Literal["y"], C[T]]], + T, + ], C[int], C[str], ) diff --git a/tests/test_fastapilike_1.py b/tests/test_fastapilike_1.py index 7a0d93e..d2d3e55 100644 --- a/tests/test_fastapilike_1.py +++ b/tests/test_fastapilike_1.py @@ -19,6 +19,7 @@ Member, Members, Param, + Params, ) from . import format_helper @@ -47,7 +48,7 @@ class _Default: type InitFnType[T] = Member[ Literal["__init__"], Callable[ - [ + Params[ Param[Literal["self"], Self], *[ Param[ diff --git a/tests/test_fastapilike_2.py b/tests/test_fastapilike_2.py index c3dc8f3..54aa59c 100644 --- a/tests/test_fastapilike_2.py +++ b/tests/test_fastapilike_2.py @@ -142,7 +142,7 @@ class Field[T: FieldArgs](typing.InitField[T]): type InitFnType[T] = typing.Member[ Literal["__init__"], Callable[ - [ + typing.Params[ typing.Param[Literal["self"], Self], *[ typing.Param[ diff --git a/tests/test_schemalike.py b/tests/test_schemalike.py index abc5b6f..1563799 100644 --- a/tests/test_schemalike.py +++ b/tests/test_schemalike.py @@ -10,6 +10,7 @@ Member, NamedParam, Param, + Params, Concat, ) @@ -46,7 +47,7 @@ class Property: Member[ Concat[Literal["get_"], p.name], Callable[ - [ + Params[ Param[Literal["self"], Schemaify[T]], NamedParam[Literal["schema"], Schema, Literal["keyword"]], ], @@ -63,16 +64,17 @@ def test_schema_like_1(): tgt = eval_typing(Schemaify[Property]) fmt = format_helper.format_class(tgt) - assert fmt == textwrap.dedent("""\ + getter_params = "self: Self, *, schema: tests.test_schemalike.Schema" + assert fmt == textwrap.dedent(f"""\ class Schemaify[tests.test_schemalike.Property]: name: str required: bool multi: bool typ: tests.test_schemalike.Type expr: tests.test_schemalike.Expression | None - def get_name(self: Self, *, schema: tests.test_schemalike.Schema) -> str: ... - def get_required(self: Self, *, schema: tests.test_schemalike.Schema) -> bool: ... - def get_multi(self: Self, *, schema: tests.test_schemalike.Schema) -> bool: ... - def get_typ(self: Self, *, schema: tests.test_schemalike.Schema) -> tests.test_schemalike.Type: ... - def get_expr(self: Self, *, schema: tests.test_schemalike.Schema) -> tests.test_schemalike.Expression | None: ... + def get_name({getter_params}) -> str: ... + def get_required({getter_params}) -> bool: ... + def get_multi({getter_params}) -> bool: ... + def get_typ({getter_params}) -> tests.test_schemalike.Type: ... + def get_expr({getter_params}) -> tests.test_schemalike.Expression | None: ... """) diff --git a/tests/test_type_dir.py b/tests/test_type_dir.py index d35e3f3..18deaf8 100644 --- a/tests/test_type_dir.py +++ b/tests/test_type_dir.py @@ -381,8 +381,8 @@ def test_type_members_func_1(): assert ( str(typ) == "\ -typing.Callable[[\ -typemap.typing.Param[typing.Literal['self'], tests.test_type_dir.Base[int], typing.Never], \ +typing.Callable[\ +typemap.typing.Params[typemap.typing.Param[typing.Literal['self'], tests.test_type_dir.Base[int], typing.Never], \ typemap.typing.Param[typing.Literal['a'], int | None, typing.Never], \ typemap.typing.Param[typing.Literal['b'], int, typing.Literal['keyword', \ 'default']]], \ @@ -403,7 +403,7 @@ def test_type_members_func_2(): assert ( str(typ) == "\ -classmethod[tests.test_type_dir.Base[int], tuple[typemap.typing.Param[typing.Literal['a'], int | None, typing.Never], typemap.typing.Param[typing.Literal['b'], ~K, typing.Never]], dict[str, int]]" +classmethod[tests.test_type_dir.Base[int], typemap.typing.Params[typemap.typing.Param[typing.Literal['a'], int | None, typing.Never], typemap.typing.Param[typing.Literal['b'], ~K, typing.Never]], dict[str, int]]" ) @@ -422,7 +422,7 @@ def test_type_members_func_3(): ) assert ( str(evaled) - == "staticmethod[tuple[typemap.typing.Param[typing.Literal['a'], int | typing.Literal['gotcha!'] | Z | None, typing.Never], typemap.typing.Param[typing.Literal['b'], ~K, typing.Never]], dict[str, int | Z]]" + == "staticmethod[typemap.typing.Params[typemap.typing.Param[typing.Literal['a'], int | typing.Literal['gotcha!'] | Z | None, typing.Never], typemap.typing.Param[typing.Literal['b'], ~K, typing.Never]], dict[str, int | Z]]" ) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 47a4352..6a42c47 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -44,6 +44,7 @@ NewProtocol, Overloaded, Param, + Params, Slice, SpecialFormEllipsis, Concat, @@ -236,11 +237,11 @@ def g(cls: type[C], x: int) -> int: ... def h(cls: type[Self], x: int) -> int: ... d = eval_typing(GetMemberType[C, Literal["f"]]) - assert d == classmethod[C, tuple[Param[Literal["x"], int]], int] + assert d == classmethod[C, Params[Param[Literal["x"], int]], int] d = eval_typing(GetMemberType[C, Literal["g"]]) - assert d == classmethod[C, tuple[Param[Literal["x"], int]], int] + assert d == classmethod[C, Params[Param[Literal["x"], int]], int] d = eval_typing(GetMemberType[C, Literal["h"]]) - assert d == classmethod[Self, tuple[Param[Literal["x"], int]], int] + assert d == classmethod[Self, Params[Param[Literal["x"], int]], int] def test_type_strings_1(): @@ -434,7 +435,7 @@ def f[TX](self, x: TX) -> OnlyIntToSet[TX]: ... assert ( f == Callable[ - [Param[Literal["self"], C], Param[Literal["x"], Vs[0]]], + Params[Param[Literal["self"], C], Param[Literal["x"], Vs[0]]], OnlyIntToSet[Vs[0]], ] ) @@ -458,7 +459,10 @@ def f[T](self, x: T) -> OnlyIntToSet[T]: ... assert ( f == Callable[ - [Param[Literal["self"], Self], Param[Literal["x"], Vs[0]]], + Params[ + Param[Literal["self"], Self], + Param[Literal["x"], Vs[0]], + ], OnlyIntToSet[Vs[0]], ] ) @@ -487,7 +491,8 @@ def f(self, x): ... assert ( mt.__args__[0] == Callable[ - [Param[Literal["self"], C], Param[Literal["x"], int]], set[int] + Params[Param[Literal["self"], C], Param[Literal["x"], int]], + set[int], ] ) @@ -500,7 +505,9 @@ def f(self, x): ... ) assert ( eval_typing(mt.__args__[1].__args__[1](int)) - == Callable[[Param[Literal["self"], C], Param[Literal["x"], int]], int] + == Callable[ + Params[Param[Literal["self"], C], Param[Literal["x"], int]], int + ] ) @@ -524,10 +531,17 @@ class C(A): ft = m.__args__[1].__args__[1] with _ensure_context(): assert ( - eval_typing(ft(A)) == Callable[[Param[Literal["self"], A]], Never] + eval_typing(ft(A)) + == Callable[Params[Param[Literal["self"], A]], Never] + ) + assert ( + eval_typing(ft(B)) + == Callable[Params[Param[Literal["self"], B]], int] + ) + assert ( + eval_typing(ft(C)) + == Callable[Params[Param[Literal["self"], C]], str] ) - assert eval_typing(ft(B)) == Callable[[Param[Literal["self"], B]], int] - assert eval_typing(ft(C)) == Callable[[Param[Literal["self"], C]], str] def test_getmember_06(): @@ -554,19 +568,22 @@ class C(A): assert ( eval_typing(ft(A)) == Callable[ - [Param[Literal["self"], A], Param[Literal["x"], Never]], None + Params[Param[Literal["self"], A], Param[Literal["x"], Never]], + None, ] ) assert ( eval_typing(ft(B)) == Callable[ - [Param[Literal["self"], B], Param[Literal["x"], int]], None + Params[Param[Literal["self"], B], Param[Literal["x"], int]], + None, ] ) assert ( eval_typing(ft(C)) == Callable[ - [Param[Literal["self"], C], Param[Literal["x"], str]], None + Params[Param[Literal["self"], C], Param[Literal["x"], str]], + None, ] ) @@ -591,9 +608,9 @@ class C(A): ft = m.__args__[1].__args__[1] with _ensure_context(): - assert eval_typing(ft(A)) == classmethod[A, tuple[()], Never] - assert eval_typing(ft(B)) == classmethod[B, tuple[()], int] - assert eval_typing(ft(C)) == classmethod[C, tuple[()], str] + assert eval_typing(ft(A)) == classmethod[A, Params[()], Never] + assert eval_typing(ft(B)) == classmethod[B, Params[()], int] + assert eval_typing(ft(C)) == classmethod[C, Params[()], str] def test_getmember_08(): @@ -620,15 +637,15 @@ class C(A): with _ensure_context(): assert ( eval_typing(ft(A)) - == classmethod[A, tuple[Param[Literal["x"], Never]], None] + == classmethod[A, Params[Param[Literal["x"], Never]], None] ) assert ( eval_typing(ft(B)) - == classmethod[B, tuple[Param[Literal["x"], int]], None] + == classmethod[B, Params[Param[Literal["x"], int]], None] ) assert ( eval_typing(ft(C)) - == classmethod[C, tuple[Param[Literal["x"], str]], None] + == classmethod[C, Params[Param[Literal["x"], str]], None] ) @@ -663,11 +680,11 @@ class B(A): with _ensure_context(): assert ( eval_typing(ft(A)) - == Callable[[Param[Literal["self"], A]], tuple[()]] + == Callable[Params[Param[Literal["self"], A]], tuple[()]] ) assert ( eval_typing(ft(B)) - == Callable[[Param[Literal["self"], B]], tuple[bool, str]] + == Callable[Params[Param[Literal["self"], B]], tuple[bool, str]] ) @@ -701,8 +718,10 @@ class B(A): ft = m.__args__[1].__args__[1] with _ensure_context(): - assert eval_typing(ft(A)) == classmethod[A, tuple[()], tuple[()]] - assert eval_typing(ft(B)) == classmethod[B, tuple[()], tuple[bool, str]] + assert eval_typing(ft(A)) == classmethod[A, Params[()], tuple[()]] + assert ( + eval_typing(ft(B)) == classmethod[B, Params[()], tuple[bool, str]] + ) def test_getarg_never(): @@ -738,11 +757,23 @@ def test_eval_getarg_callable_old(): t = Callable[..., str] args = eval_typing(GetArg[t, Callable, 0]) - assert args == SpecialFormEllipsis + assert ( + args + == Params[ + Param[Literal[None], Any, Literal["*"]], + Param[Literal[None], Any, Literal["**"]], + ] + ) t = Callable args = eval_typing(GetArg[t, Callable, 0]) - assert args == SpecialFormEllipsis + assert ( + args + == Params[ + Param[Literal[None], Any, Literal["*"]], + Param[Literal[None], Any, Literal["**"]], + ] + ) t = Callable args = eval_typing(GetArg[t, Callable, 1]) @@ -752,29 +783,36 @@ def test_eval_getarg_callable_old(): def test_eval_getarg_callable_01(): t = Callable[[int, str], str] args = eval_typing(GetArg[t, Callable, Literal[0]]) - assert ( - args - == tuple[ - Param[Literal[None], int, Never], Param[Literal[None], str, Never] - ] - ) + assert args == Params[Param[Literal[None], int], Param[Literal[None], str]] t = Callable[int, str] args = eval_typing(GetArg[t, Callable, Literal[0]]) - assert args == tuple[Param[Literal[None], int, Never]] + assert args == Params[Param[Literal[None], int]] t = Callable[[], str] args = eval_typing(GetArg[t, Callable, Literal[0]]) - assert args == tuple[()] + assert args == Params[()] - # XXX: Is this what we want? Or should it be *args, **kwargs + # ... becomes *args: Any, **kwargs: Any t = Callable[..., str] args = eval_typing(GetArg[t, Callable, Literal[0]]) - assert args == SpecialFormEllipsis + assert ( + args + == Params[ + Param[Literal[None], Any, Literal["*"]], + Param[Literal[None], Any, Literal["**"]], + ] + ) t = Callable args = eval_typing(GetArg[t, Callable, Literal[0]]) - assert args == SpecialFormEllipsis + assert ( + args + == Params[ + Param[Literal[None], Any, Literal["*"]], + Param[Literal[None], Any, Literal["**"]], + ] + ) t = Callable args = eval_typing(GetArg[t, Callable, Literal[1]]) @@ -841,7 +879,7 @@ def f(self, x: int, /, y: int, *, z: int) -> int: ... t = eval_typing(GetArg[f, Callable, Literal[0]]) assert ( t - == tuple[ + == Params[ Param[Literal["self"], C, Literal["positional"]], Param[Literal["x"], int, Literal["positional"]], Param[Literal["y"], int], @@ -855,7 +893,7 @@ def f(self, x: int, /, y: int, *, z: int) -> int: ... t = eval_typing(GetArg[f, Callable, Literal[0]]) assert ( t - == tuple[ + == Params[ Param[Literal["self"], Self, Literal["positional"]], Param[Literal["x"], int, Literal["positional"]], Param[Literal["y"], int], @@ -878,7 +916,7 @@ def f(cls, x: int, /, y: int, *, z: int) -> int: ... t = eval_typing(GetArg[f, classmethod, Literal[1]]) assert ( t - == tuple[ + == Params[ Param[Literal["x"], int, Literal["positional"]], Param[Literal["y"], int], Param[Literal["z"], int, Literal["keyword"]], @@ -892,7 +930,7 @@ def f(cls, x: int, /, y: int, *, z: int) -> int: ... t = eval_typing(GetArg[f, classmethod, Literal[1]]) assert ( t - == tuple[ + == Params[ Param[Literal["x"], int, Literal["positional"]], Param[Literal["y"], int], Param[Literal["z"], int, Literal["keyword"]], @@ -912,7 +950,7 @@ def f(x: int, /, y: int, *, z: int) -> int: ... t = eval_typing(GetArg[f, staticmethod, Literal[0]]) assert ( t - == tuple[ + == Params[ Param[Literal["x"], int, Literal["positional"]], Param[Literal["y"], int], Param[Literal["z"], int, Literal["keyword"]], @@ -925,7 +963,7 @@ def f(x: int, /, y: int, *, z: int) -> int: ... t = eval_typing(GetArg[f, staticmethod, Literal[0]]) assert ( t - == tuple[ + == Params[ Param[Literal["x"], int, Literal["positional"]], Param[Literal["y"], int], Param[Literal["z"], int, Literal["keyword"]], @@ -942,7 +980,7 @@ class C: f = eval_typing(GetMethodLike[IndirectProtocol[C], Literal["f"]]) t = eval_typing(GetArg[f, Callable, Literal[0]]) - assert t == tuple[Param[Literal[None], int, Never],] + assert t == Params[Param[Literal[None], int]] t = eval_typing(GetArg[f, Callable, Literal[1]]) assert t is int @@ -1273,7 +1311,7 @@ def test_eval_getarg_custom_08(): def test_eval_getargs_generic_callable_01(): T = TypeVar("T") t = GenericCallable[ - tuple[T], lambda T: Callable[[Param[Literal["x"], T]], int] + tuple[T], lambda T: Callable[Params[Param[Literal["x"], T]], int] ] args = eval_typing(GetArgs[t, GenericCallable]) assert args == tuple[tuple[T]] @@ -1792,7 +1830,7 @@ def test_callable_to_signature_01(): # **kwargs: int # ) -> int: callable_type = Callable[ - [ + Params[ Param[None, int], Param[Literal["b"], int], Param[Literal["c"], int, Literal["default"]], @@ -1815,58 +1853,6 @@ def test_callable_to_signature_01(): ) -def test_callable_to_signature_02(): - from typemap.type_eval._eval_operators import _callable_type_to_signature - - class C: - pass - - callable_type = classmethod[ - C, - tuple[ - Param[None, int], - 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, - ] - sig = _callable_type_to_signature(callable_type) - assert str(sig) == ( - '(cls: tests.test_type_eval.test_callable_to_signature_02..C, ' - '_arg1: int, /, b: int, c: int = ..., *args: int, ' - 'd: int, e: int = ..., **kwargs: int) -> int' - ) - - -def test_callable_to_signature_03(): - from typemap.type_eval._eval_operators import _callable_type_to_signature - - class C: - pass - - callable_type = staticmethod[ - tuple[ - Param[None, int], - 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, - ] - sig = _callable_type_to_signature(callable_type) - assert str(sig) == ( - '(_arg0: int, /, b: int, c: int = ..., *args: int, ' - 'd: int, e: int = ..., **kwargs: int) -> int' - ) - - def test_new_protocol_with_methods_01(): class C: def member_method(self, x: int) -> int: ... @@ -1892,18 +1878,19 @@ def test_new_protocol_with_methods_02(): Member[ Literal["member_method"], Callable[ - [Param[Literal["self"], Self], Param[Literal["x"], int]], int + Params[Param[Literal["self"], Self], Param[Literal["x"], int]], + int, ], Literal["ClassVar"], ], Member[ Literal["class_method"], - classmethod[Self, tuple[Param[Literal["x"], int]], int], + classmethod[Self, Params[Param[Literal["x"], int]], int], Literal["ClassVar"], ], Member[ Literal["static_method"], - staticmethod[tuple[Param[Literal["x"], int]], int], + staticmethod[Params[Param[Literal["x"], int]], int], Literal["ClassVar"], ], ] @@ -1966,7 +1953,7 @@ def g(self) -> int: ... # omitted Literal["__init_subclass__"], classmethod[ A, - tuple[()], + Params[()], UpdateClass[ Member[Literal["a2"], str], Member[Literal["b1"], str], @@ -1979,7 +1966,7 @@ def g(self) -> int: ... # omitted ], Member[ Literal["f"], - Callable[[Param[Literal["self"], A]], int], + Callable[Params[Param[Literal["self"], A]], int], Literal["ClassVar"], object, A, @@ -2058,7 +2045,7 @@ def g(self) -> int: ... # omitted Member[Literal["b2"], str, Never, Never, B], Member[ Literal["f"], - Callable[[Param[Literal["self"], A]], int], + Callable[Params[Param[Literal["self"], A]], int], Literal["ClassVar"], object, A, @@ -2121,7 +2108,7 @@ def g(self) -> int: ... # omitted Member[Literal["b"], set[str], Never, Never, B], Member[ Literal["f"], - Callable[[Param[Literal["self"], A]], int], + Callable[Params[Param[Literal["self"], A]], int], Literal["ClassVar"], object, A, @@ -2178,7 +2165,7 @@ def g(self) -> int: ... Literal["__init_subclass__"], classmethod[ A, - tuple[()], + Params[()], None, ], Literal["ClassVar"], @@ -2187,14 +2174,14 @@ def g(self) -> int: ... ], Member[ Literal["f"], - Callable[[Param[Literal["self"], A]], int], + Callable[Params[Param[Literal["self"], A]], int], Literal["ClassVar"], object, A, ], Member[ Literal["g"], - Callable[[Param[Literal["self"], B]], int], + Callable[Params[Param[Literal["self"], B]], int], Literal["ClassVar"], object, B, @@ -2440,14 +2427,20 @@ def g(self) -> str: ... Member[Literal["b"], str, Never, Never, B], Member[ Literal["f"], - Callable[[Param[Literal["self"], Self]], int], + Callable[ + Params[Param[Literal["self"], Self]], + int, + ], Literal["ClassVar"], object, B, ], Member[ Literal["g"], - Callable[[Param[Literal["self"], Self]], str], + Callable[ + Params[Param[Literal["self"], Self]], + str, + ], Literal["ClassVar"], object, B, diff --git a/typemap/type_eval/_apply_generic.py b/typemap/type_eval/_apply_generic.py index 02f493a..fc43df1 100644 --- a/typemap/type_eval/_apply_generic.py +++ b/typemap/type_eval/_apply_generic.py @@ -86,6 +86,8 @@ def substitute(ty, args): ty, (typing_GenericAlias, types.GenericAlias, types.UnionType) ): return ty.__origin__[*[substitute(t, args) for t in ty.__args__]] + elif isinstance(ty, list): + return [substitute(t, args) for t in ty] else: return ty diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index b3cfc41..2618505 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -44,6 +44,7 @@ NewTypedDict, Overloaded, Param, + Params, RaiseError, Slice, SpecialFormEllipsis, @@ -491,69 +492,51 @@ def __repr__(self): _DUMMY_DEFAULT = _DummyDefault() -def _callable_type_to_signature(callable_type: object) -> inspect.Signature: - """Convert a Callable type with Param specs to an inspect.Signature. +def _unwrap_params(param_types) -> list: + """Unwrap params into a list of Param types. - The callable_type should be of the form: - Callable[ - [ - Param[name, type, quals], - ... - ], - return_type, - ] - - Where: - - name is None for positional-only or variadic params, or a string - - type is the parameter type annotation - - quals is a Literal with any of: "*", "**", "keyword", "default" - or Never if no qualifiers + Accepts Params[...] (extended format), Ellipsis (any params), + or a list/tuple of plain types (standard format, converted to + positional-only Params). """ - args = typing.get_args(callable_type) - if ( - isinstance(callable_type, types.GenericAlias) - and callable_type.__origin__ is classmethod - ): - if len(args) != 3: - raise TypeError( - f"Expected classmethod[cls, [...], ret], got {callable_type}" - ) - - receiver, param_types, return_type = typing.get_args(callable_type) - param_types = [ - Param[ - typing.Literal["cls"], - receiver, # type: ignore[valid-type] - typing.Literal["positional"], - ], - *param_types.__args__, - ] - - elif ( - isinstance(callable_type, types.GenericAlias) - and callable_type.__origin__ is staticmethod - ): - if len(args) != 2: - raise TypeError( - f"Expected staticmethod[...], ret], got {callable_type}" - ) + if typing.get_origin(param_types) is Params: + return list(typing.get_args(param_types)) - param_types, return_type = typing.get_args(callable_type) - param_types = [ - *param_types.__args__, + if param_types is ...: + return [ + Param[typing.Literal[None], typing.Any, typing.Literal["*"]], + Param[typing.Literal[None], typing.Any, typing.Literal["**"]], ] + if isinstance(param_types, (list, tuple)): + items = list(param_types) else: - if len(args) != 2: + raise TypeError( + f"Expected Params[...] or list of types, got {param_types}" + ) + # Error if someone passes Param types without Params wrapper + for t in items: + if typing.get_origin(t) is Param: raise TypeError( - f"Expected Callable[[...], ret], got {callable_type}" + f"Param types must be wrapped in Params[...], got [{t}, ...]" ) + # Convert standard types to positional-only Params + return [ + Param[typing.Literal[None], t] # type: ignore[valid-type] + for t in items + ] - param_types, return_type = args - # Handle the case where param_types is a list of Param types - if not isinstance(param_types, (list, tuple)): - raise TypeError(f"Expected list of Param types, got {param_types}") +def _callable_type_to_signature(callable_type: object) -> inspect.Signature: + """Convert a Callable[Params[...], ret] type to an inspect.Signature.""" + args = typing.get_args(callable_type) + if len(args) != 2: + raise TypeError( + f"Expected Callable[Params[...], ret], got {callable_type}" + ) + + raw_params, return_type = args + param_types = _unwrap_params(raw_params) parameters: list[inspect.Parameter] = [] saw_keyword_only = False @@ -684,10 +667,11 @@ def _callable_type_to_method(name, typ, ctx): if head is classmethod: # XXX: handle other amounts - cls, params, ret = typing.get_args(typ) + cls, raw_params, ret = typing.get_args(typ) + param_list = _unwrap_params(raw_params) # We have to make class positional only if there is some other # positional only argument. Annoying! - has_pos_only = any(_is_pos_only(p) for p in typing.get_args(params)) + has_pos_only = any(_is_pos_only(p) for p in param_list) quals = typing.Literal["positional"] if has_pos_only else typing.Never # Override the receiver type with type[Self]. if name == "__init_subclass__" and isinstance(cls, typing.TypeVar): @@ -696,12 +680,14 @@ def _callable_type_to_method(name, typ, ctx): else: cls_typ = type[typing.Self] # type: ignore[name-defined] cls_param = Param[typing.Literal["cls"], cls_typ, quals] - typ = typing.Callable[[cls_param] + list(typing.get_args(params)), ret] + typ = typing.Callable[Params[cls_param, *param_list], ret] elif head is staticmethod: - params, ret = typing.get_args(typ) - typ = typing.Callable[list(typing.get_args(params)), ret] + raw_params, ret = typing.get_args(typ) + param_list = _unwrap_params(raw_params) + typ = typing.Callable[Params[*param_list], ret] else: - params, ret = typing.get_args(typ) + raw_params, ret = typing.get_args(typ) + param_list = _unwrap_params(raw_params) # Override the annotations for methods # - use Self for the "self" param, otherwise the fully qualified cls # name gets used. This ends up being long and annoying to handle. @@ -709,7 +695,7 @@ def _callable_type_to_method(name, typ, ctx): # - __init__ should return None regardless of what the user says. # The default return type for methods is Any, so this also handles # the un-annotated case. - params = [ + param_list = [ ( p if typing.get_args(p)[0] != typing.Literal["self"] @@ -719,10 +705,10 @@ def _callable_type_to_method(name, typ, ctx): typing.get_args(p)[2], ] ) - for p in params + for p in param_list ] ret = type(None) if name == "__init__" else ret - typ = typing.Callable[params, ret] + typ = typing.Callable[Params[*param_list], ret] head = lambda x: x func = _signature_to_function(name, _callable_type_to_signature(typ)) @@ -777,15 +763,13 @@ def _ann(x): ret = _ann(sig.return_annotation) - # TODO: Is doing the tuple for staticmethod/classmethod legit? - # Putting a list in makes it unhashable... f: typing.Any # type: ignore[annotation-unchecked] if isinstance(func, staticmethod): - f = staticmethod[tuple[*params], ret] + f = staticmethod[Params[*params], ret] elif isinstance(func, classmethod): - f = classmethod[specified_receiver, tuple[*params[1:]], ret] + f = classmethod[specified_receiver, Params[*params[1:]], ret] else: - f = typing.Callable[params, ret] + f = typing.Callable[Params[*params], ret] return f @@ -813,17 +797,14 @@ def _create_generic_callable_lambda( ) else: - # Callable params are stored as a list + # Callable params are stored as Params[...] params, ret = typing.get_args(f) return lambda *vs: typing.Callable[ - [ - _apply_generic.substitute( - p, - dict(zip(type_params, vs, strict=True)), - ) - for p in params - ], + _apply_generic.substitute( + params, + dict(zip(type_params, vs, strict=True)), + ), _apply_generic.substitute( ret, dict(zip(type_params, vs, strict=True)) ), @@ -916,18 +897,7 @@ def _fix_callable_args(base, args): if idx >= len(args): return args args = list(args) - special = _fix_type(args[idx]) - if typing.get_origin(special) is tuple: - args[idx] = tuple[ - *[ - ( - t - if typing.get_origin(t) is Param - else Param[typing.Literal[None], t] - ) - for t in typing.get_args(special) - ] - ] + args[idx] = Params[*_unwrap_params(args[idx])] return tuple(args) @@ -966,12 +936,14 @@ def _get_args(tp, base, ctx) -> typing.Any: def _fix_type(tp): """Fix up a type getting returned from GetArg - In particular, this means turning a list into a tuple of the - Paramified list elements and turning ... into SpecialFormEllipsis. + In particular, this means turning a list into Params (for + Callable parameter lists) or a tuple, and turning ... into + SpecialFormEllipsis. """ - if isinstance(tp, (tuple, list)): - # XXX: Can we always do this or is it a problem + if isinstance(tp, list): + return Params[*tp] + elif isinstance(tp, tuple): return tuple[*tp] elif tp is ...: return SpecialFormEllipsis @@ -1063,7 +1035,13 @@ def _get_defaults(base_head): # Callable and tuple need to produce a SpecialFormEllipsis for arg # 0 and 1, respectively. if base_head is collections.abc.Callable: - return (SpecialFormEllipsis, typing.Any) + return ( + Params[ + Param[typing.Literal[None], typing.Any, typing.Literal["*"]], + Param[typing.Literal[None], typing.Any, typing.Literal["**"]], + ], + typing.Any, + ) elif base_head is tuple: return (typing.Any, SpecialFormEllipsis) diff --git a/typemap/typing.py b/typemap/typing.py index 589b804..b258951 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -219,6 +219,20 @@ class Param[N: str | None, T, Q: ParamQuals = typing.Never]: type KwargsParam[T] = Param[Literal[None], T, Literal["**"]] +class Params: + """A concrete parameter specification for extended Callable types. + + Params[Param[...], ...] can be used as the first argument to + Callable, like ParamSpec. It survives the typing.Callable round-trip + by using _ConcatenateGenericAlias internally. + """ + + def __class_getitem__(cls, params): + if not isinstance(params, tuple): + params = (params,) + return typing._ConcatenateGenericAlias(cls, params) + + type GetName[T: Member | Param] = T.name type GetType[T: Member | Param] = T.type type GetQuals[T: Member | Param] = T.quals