From 225d19aa7df7639b51cdcf1231ad5bc65cd5cf75 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Mon, 9 Feb 2026 18:23:55 -0800 Subject: [PATCH 1/5] Remove unused _flatten_class_explicit. --- typemap/type_eval/_apply_generic.py | 80 ----------------------------- 1 file changed, 80 deletions(-) diff --git a/typemap/type_eval/_apply_generic.py b/typemap/type_eval/_apply_generic.py index c8ec175..28132d4 100644 --- a/typemap/type_eval/_apply_generic.py +++ b/typemap/type_eval/_apply_generic.py @@ -405,84 +405,4 @@ def _type_repr(t: Any) -> str: return repr(t) -# TODO: Potentially most of this could be ripped out. The internals -# don't use this at all, it's only used by format_class. -def _flatten_class_explicit( - cls: type[Any], ctx: _eval_typing.EvalContext -) -> type[_eval_typing._EvalProxy]: - cls_boxed = box(cls) - mro_boxed = cls_boxed.mro - - # TODO: I think we want to create the whole mro chain... - # before we evaluate the contents? - - # FIXME: right now we flatten out all the attributes... but should we?? - # XXX: Yeah, a lot of work is put into copying everything into every - # class and it is not worth it, at all. - - new = {} - - # Run through the mro and populate everything - for boxed in reversed(mro_boxed): - # We create it early so we can add it to seen, to handle recursion - # XXX: currently we are doing this even for types with no generics... - # that simplifies the flow... - probably keep it this way until - # we stop flattening attributes into every class - name = boxed.cls.__name__ - cboxed: Any - - args = tuple(boxed.args.values()) - args_str = ", ".join(_type_repr(a) for a in args) - fullname = f"{name}[{args_str}]" if args_str else name - cboxed = type( - fullname, - (_eval_typing._EvalProxy,), - { - "__module__": boxed.cls.__module__, - "__name__": fullname, - "__origin__": boxed.cls, - "__local_args__": args, - }, - ) - new[boxed] = cboxed - - annos: dict[str, Any] = {} - dct: dict[str, Any] = {} - sources: dict[str, Any] = {} - - cboxed.__local_annotations__, cboxed.__local_defns__ = get_local_defns( - boxed - ) - for base in reversed(boxed.mro): - cbase = new[base] - annos.update(cbase.__local_annotations__) - dct.update(cbase.__local_defns__) # uh. - for k in [*cbase.__local_annotations__, *cbase.__local_defns__]: - sources[k] = cbase - - cboxed.__defn_names__ = set(dct) - cboxed.__annotations__ = annos - cboxed.__defn_sources__ = sources - cboxed.__generalized_mro__ = [new[b] for b in boxed.mro] - - for k, v in dct.items(): - setattr(cboxed, k, v) - - # Run through the mro again and evaluate everything - for cboxed in new.values(): - for k, v in cboxed.__annotations__.items(): - cboxed.__annotations__[k] = _eval_typing._eval_types(v, ctx=ctx) - - for k in cboxed.__defn_names__: - v = cboxed.__dict__[k] - setattr(cboxed, k, _eval_typing._eval_types(v, ctx=ctx)) - - return new[cls_boxed] - - -def flatten_class_explicit(obj: typing.Any): - with _eval_typing._ensure_context() as ctx: - return _flatten_class_explicit(obj, ctx) - - flatten_class = flatten_class_new_proto From 26003c800f2b1bd1c6996c7eb2bf9ab94de3e4c0 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Mon, 9 Feb 2026 18:19:39 -0800 Subject: [PATCH 2/5] Add Overloaded. --- typemap/typing.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/typemap/typing.py b/typemap/typing.py index 8a89f26..9a65dc6 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -101,6 +101,10 @@ def __class_getitem__(cls, params): return _GenericCallableGenericAlias(cls, (typevars, func)) +class Overloaded[*Callables]: + pass + + ### From 8dfa765f7fe06a83287e91b8432581a18bed9a64 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Mon, 9 Feb 2026 18:28:36 -0800 Subject: [PATCH 3/5] Add test. --- tests/test_type_eval.py | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 79a3dd3..a3f122a 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -16,6 +16,7 @@ TypeVar, Union, get_args, + overload, ) import pytest @@ -45,6 +46,7 @@ Member, Members, NewProtocol, + Overloaded, Param, Slice, SpecialFormEllipsis, @@ -466,6 +468,46 @@ def f[T](self, x: T) -> OnlyIntToSet[T]: ... ) +def test_getmember_04(): + class C: + @overload + def f(self, x: int) -> set[int]: ... + + @overload + def f[T](self, x: T) -> T: ... + + def f(self, x): ... + + m = eval_typing(GetMember[C, Literal["f"]]) + mt = eval_typing(GetType[m]) + assert mt.__origin__ is Overloaded + assert len(mt.__args__) == 2 + + # Non-generic overload + assert ( + eval_typing(IsAssignable[GetArg[mt, Overloaded, Literal[0]], Callable]) + == _BoolLiteral[True] + ) + assert ( + mt.__args__[0] + == Callable[ + [Param[Literal["self"], C], Param[Literal["x"], int]], set[int] + ] + ) + + # Generic overload + assert ( + eval_typing( + IsAssignable[GetArg[mt, Overloaded, Literal[1]], GenericCallable] + ) + == _BoolLiteral[True] + ) + assert ( + eval_typing(mt.__args__[1].__args__[1](int)) + == Callable[[Param[Literal["self"], C], Param[Literal["x"], int]], int] + ) + + def test_getarg_never(): d = eval_typing(GetArg[Never, object, Literal[0]]) assert d is Never From e2b6548ebfc75c20a1b84d2e1f0d630cae12395e Mon Sep 17 00:00:00 2001 From: dnwpark Date: Mon, 9 Feb 2026 18:28:47 -0800 Subject: [PATCH 4/5] Implement. --- typemap/type_eval/_apply_generic.py | 28 +++++++++++++++++++++++++++- typemap/type_eval/_eval_operators.py | 12 ++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/typemap/type_eval/_apply_generic.py b/typemap/type_eval/_apply_generic.py index 28132d4..88433d2 100644 --- a/typemap/type_eval/_apply_generic.py +++ b/typemap/type_eval/_apply_generic.py @@ -291,7 +291,14 @@ def _resolved_function_signature(func, args): return sig -def get_local_defns(boxed: Boxed) -> tuple[dict[str, Any], dict[str, Any]]: +def get_local_defns( + boxed: Boxed, +) -> tuple[ + dict[str, Any], + dict[ + str, types.FunctionType | classmethod | staticmethod | WrappedOverloaded + ], +]: from typemap.typing import GenericCallable annos: dict[str, Any] = {} @@ -327,6 +334,8 @@ def get_local_defns(boxed: Boxed) -> tuple[dict[str, Any], dict[str, Any]]: # XXX: This is totally wrong; we still need to do # substitute in class vars local_fn = stuff + elif overloaded := _is_overloaded_function(stuff): + local_fn = overloaded # If we got stuck, we build a GenericCallable that # computes the type once it has been given type @@ -370,6 +379,23 @@ def lam(*vs): return annos, dct +@dataclasses.dataclass(frozen=True) +class WrappedOverloaded: + functions: tuple[types.FunctionType, ...] + + +def _is_overloaded_function(func): + module_overload_registry = typing._overload_registry[func.__module__] + if not module_overload_registry: + return None + + func_overload_registry = module_overload_registry[func.__qualname__] + if not func_overload_registry: + return + + return WrappedOverloaded(tuple(func_overload_registry.values())) + + def flatten_class_new_proto(cls: type) -> type: # This is a hacky version of flatten_class that works by using # NewProtocol on Members! diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 60a12dd..419db36 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -39,6 +39,7 @@ Member, Members, NewProtocol, + Overloaded, Param, RaiseError, Slice, @@ -169,6 +170,17 @@ def get_annotated_method_hints(cls, *, ctx): object, acls, ) + elif isinstance(attr, _apply_generic.WrappedOverloaded): + overloads = [ + _function_type(_eval_types(of, ctx), receiver_type=acls) + for of in attr.functions + ] + hints[name] = ( + Overloaded[*overloads], + ("ClassVar",), + object, + acls, + ) return hints From e277471063b65204d5a9ecb5c9fbd292afe4b8f0 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Wed, 11 Feb 2026 11:46:46 -0800 Subject: [PATCH 5/5] Use typing.get_overloads. --- typemap/type_eval/_apply_generic.py | 22 +++++----------------- typemap/type_eval/_eval_operators.py | 2 +- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/typemap/type_eval/_apply_generic.py b/typemap/type_eval/_apply_generic.py index 88433d2..47db9f0 100644 --- a/typemap/type_eval/_apply_generic.py +++ b/typemap/type_eval/_apply_generic.py @@ -296,7 +296,7 @@ def get_local_defns( ) -> tuple[ dict[str, Any], dict[ - str, types.FunctionType | classmethod | staticmethod | WrappedOverloaded + str, types.FunctionType | classmethod | staticmethod | WrappedOverloads ], ]: from typemap.typing import GenericCallable @@ -334,8 +334,8 @@ def get_local_defns( # XXX: This is totally wrong; we still need to do # substitute in class vars local_fn = stuff - elif overloaded := _is_overloaded_function(stuff): - local_fn = overloaded + elif overloads := typing.get_overloads(stuff): + local_fn = WrappedOverloads(tuple(overloads)) # If we got stuck, we build a GenericCallable that # computes the type once it has been given type @@ -380,20 +380,8 @@ def lam(*vs): @dataclasses.dataclass(frozen=True) -class WrappedOverloaded: - functions: tuple[types.FunctionType, ...] - - -def _is_overloaded_function(func): - module_overload_registry = typing._overload_registry[func.__module__] - if not module_overload_registry: - return None - - func_overload_registry = module_overload_registry[func.__qualname__] - if not func_overload_registry: - return - - return WrappedOverloaded(tuple(func_overload_registry.values())) +class WrappedOverloads: + functions: tuple[typing.Callable[..., Any], ...] def flatten_class_new_proto(cls: type) -> type: diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 419db36..46c3747 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -170,7 +170,7 @@ def get_annotated_method_hints(cls, *, ctx): object, acls, ) - elif isinstance(attr, _apply_generic.WrappedOverloaded): + elif isinstance(attr, _apply_generic.WrappedOverloads): overloads = [ _function_type(_eval_types(of, ctx), receiver_type=acls) for of in attr.functions