From 74c011a79aecfe74ee131c89ddb72e9fa5971c20 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Thu, 15 Jan 2026 08:35:58 -0800 Subject: [PATCH 1/4] Overwrite annotations for normal class methods. --- typemap/type_eval/_eval_operators.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 80b8a38..8ea9bc5 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -431,6 +431,21 @@ def _callable_type_to_method(name, typ): params, ret = typing.get_args(typ) typ = typing.Callable[list(typing.get_args(params)), ret] else: + params, ret = typing.get_args(typ) + params = [ + ( + p + if typing.get_args(p)[0] != typing.Literal["self"] + else Param[ + typing.Literal["self"], + typing.Self, + typing.get_args(p)[2], + ] + ) + for p in params + ] + ret = type(None) if name == "__init__" else ret + typ = typing.Callable[params, ret] head = lambda x: x func = _signature_to_function(name, _callable_type_to_signature(typ)) From d811307bf1d58c539c1303bb937f90e3c4b45d06 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Thu, 15 Jan 2026 08:37:07 -0800 Subject: [PATCH 2/4] Fetch binding for ordinary TypeVars. --- typemap/type_eval/_eval_call.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/typemap/type_eval/_eval_call.py b/typemap/type_eval/_eval_call.py index a1291a6..f874a25 100644 --- a/typemap/type_eval/_eval_call.py +++ b/typemap/type_eval/_eval_call.py @@ -65,6 +65,12 @@ def _get_bound_type_args( ): tp = typing.TypedDict(f"**{param.name}", bound.kwargs) # type: ignore[misc, operator] vars[tv.__name__] = tp + elif ( + isinstance(param.annotation, typing.TypeVar) + and param.name in bound.arguments + ): + param_value = bound.arguments[param.name] + vars[param.annotation.__name__] = param_value # TODO: simple bindings to other variables too return vars From 490dd42a4a5c45d9ee1443a60224bbee823b2691 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Thu, 15 Jan 2026 08:38:58 -0800 Subject: [PATCH 3/4] Update tests. --- tests/test_call.py | 22 ++++++++++++++++++++++ tests/test_schemalike.py | 10 +++++----- tests/test_type_dir.py | 4 ++-- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/tests/test_call.py b/tests/test_call.py index e517bc5..e983aa7 100644 --- a/tests/test_call.py +++ b/tests/test_call.py @@ -50,3 +50,25 @@ class **kwargs: b: typing.Literal[2] c: typing.Literal['aaa'] """) + + +class Wrapped[T]: # noqa: B903 + value: T + + def __init__(self, value: T): + self.value = value + + +def wrapped[T](value: T) -> Wrapped[T]: + return Wrapped[T](value) + + +def test_call_3(): + ret = eval_call(wrapped, 1) + fmt = format_helper.format_class(ret) + + assert fmt == textwrap.dedent("""\ + class Wrapped[typing.Literal[1]]: + value: typing.Literal[1] + def __init__(self: Self, value: Literal[1]) -> None: ... + """) diff --git a/tests/test_schemalike.py b/tests/test_schemalike.py index 0ef0127..0ddf396 100644 --- a/tests/test_schemalike.py +++ b/tests/test_schemalike.py @@ -72,9 +72,9 @@ class Schemaify[tests.test_schemalike.Property]: multi: bool typ: tests.test_schemalike.Type expr: tests.test_schemalike.Expression | None - def get_name(self: Schemaify[tests.test_schemalike.Property], *, schema: tests.test_schemalike.Schema) -> str: ... - def get_required(self: Schemaify[tests.test_schemalike.Property], *, schema: tests.test_schemalike.Schema) -> bool: ... - def get_multi(self: Schemaify[tests.test_schemalike.Property], *, schema: tests.test_schemalike.Schema) -> bool: ... - def get_typ(self: Schemaify[tests.test_schemalike.Property], *, schema: tests.test_schemalike.Schema) -> tests.test_schemalike.Type: ... - def get_expr(self: Schemaify[tests.test_schemalike.Property], *, schema: tests.test_schemalike.Schema) -> 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: ... """) diff --git a/tests/test_type_dir.py b/tests/test_type_dir.py index 9a75da0..8fffd26 100644 --- a/tests/test_type_dir.py +++ b/tests/test_type_dir.py @@ -203,8 +203,8 @@ class Final: fin: typing.Final[int] x: tests.test_type_dir.Wrapper[int | None] ordinary: str - def foo(self: tests.test_type_dir.Base[int], a: int | None, *, b: int = ...) -> dict[str, int]: ... - def base[Z](self: tests.test_type_dir.Base[int], a: int | Z | None, b: ~K) -> dict[str, int | Z]: ... + def foo(self: Self, a: int | None, *, b: int = ...) -> dict[str, int]: ... + def base[Z](self: Self, a: int | Z | None, b: ~K) -> dict[str, int | Z]: ... @classmethod def cbase(cls: type[tests.test_type_dir.Base[int]], a: int | None, b: ~K) -> dict[str, int]: ... @staticmethod From 03d297076f3aa8c44f60af0f6718d881b960232b Mon Sep 17 00:00:00 2001 From: dnwpark Date: Thu, 15 Jan 2026 10:24:32 -0800 Subject: [PATCH 4/4] Add comment. --- typemap/type_eval/_eval_operators.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 8ea9bc5..c9c4349 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -432,6 +432,13 @@ def _callable_type_to_method(name, typ): typ = typing.Callable[list(typing.get_args(params)), ret] else: params, ret = typing.get_args(typ) + # 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. + # GetDefiner can always be used to get the actual type. + # - __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 = [ ( p