From d506869b656936c8586cfd9c2a640a23aa88a467 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Wed, 11 Feb 2026 16:36:09 -0800 Subject: [PATCH 1/3] Add tests. --- tests/test_type_eval.py | 52 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 09c407c..b8bf689 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -2210,6 +2210,55 @@ class D[T](C[T]): ) +def test_update_class_members_11(): + class A: + a: int + + def __init_subclass__[T]( + cls: type[T], + ) -> UpdateClass[*Members[T]]: + super().__init_subclass__() + + def f(self) -> int: ... + + class B(A): + b: str + + def g(self) -> str: ... + + attrs = eval_typing(Attrs[B]) + assert ( + attrs + == tuple[ + Member[Literal["a"], int, Never, Never, B], + Member[Literal["b"], str, Never, Never, B], + ] + ) + + members = eval_typing(MembersExceptInitSubclass[B]) + assert ( + members + == tuple[ + Member[Literal["a"], int, Never, Never, B], + Member[Literal["b"], str, Never, Never, B], + Member[ + Literal["f"], + Callable[[Param[Literal["self"], Self]], int], + Literal["ClassVar"], + object, + B, + ], + Member[ + Literal["g"], + Callable[[Param[Literal["self"], Self]], str], + Literal["ClassVar"], + object, + B, + ], + ] + ) + + def test_update_class_inheritance_01(): # current class init subclass is not applied class A: @@ -2327,7 +2376,6 @@ class C(B[float]): assert eval_typing(GetArg[C, A, Literal[1]]) is float -@pytest.mark.xfail(reason="TODO") def test_update_class_empty_01(): class A: a: int @@ -2341,7 +2389,7 @@ class B(A): b: int attrs = eval_typing(Attrs[B]) - assert attrs == tuple[()] + assert attrs == tuple[Member[Literal["a"], int, Never, Never, A]] ############## From 99bcf95fb950c411f7a8d943f71b5a7de99fc342 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Wed, 11 Feb 2026 16:36:51 -0800 Subject: [PATCH 2/3] Fix empty Members not being handled properly. --- typemap/type_eval/_eval_operators.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index fce90e6..145abc9 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -192,7 +192,8 @@ def _eval_init_subclass( """Get type after all __init_subclass__ with UpdateClass are evaluated.""" for abox in box.mro[1:]: # Skip the type itself with _child_context() as ctx: - if ms := _get_update_class_members(box, abox, ctx=ctx): + ms = _get_update_class_members(box, abox, ctx=ctx) + if ms is not None: nbox = _apply_generic.box( _create_updated_class(box, ms, ctx=ctx) ) From c80f898fa00c106fd2818405859b26af928c340c Mon Sep 17 00:00:00 2001 From: dnwpark Date: Wed, 11 Feb 2026 16:37:46 -0800 Subject: [PATCH 3/3] Don't evaluate generic callable annotations. --- typemap/type_eval/_eval_operators.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 145abc9..bf513d6 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -15,6 +15,7 @@ from typemap.type_eval import _apply_generic, _typing_inspect from typemap.type_eval._eval_typing import ( _child_context, + _eval_args, _eval_types, EvalContext, ) @@ -209,7 +210,7 @@ def _get_update_class_members( box: _apply_generic.Boxed, boxed_base: _apply_generic.Boxed, ctx: EvalContext, -) -> list[Member] | None: +) -> typing.Sequence[Member] | None: cls = box.cls # Get __init_subclass__ from the base class's origin if base is generic. @@ -268,13 +269,13 @@ def _get_update_class_members( _typing_inspect.is_generic_alias(evaled_ret) and typing.get_origin(evaled_ret) is UpdateClass ): - return [m for m in typing.get_args(evaled_ret)] + return _eval_args(typing.get_args(evaled_ret), ctx) return None def _create_updated_class( - box: _apply_generic.Boxed, ms: list[Member], ctx: EvalContext + box: _apply_generic.Boxed, ms: typing.Sequence[Member], ctx: EvalContext ) -> type: t = box.cls dct: dict[str, object] = {} @@ -290,9 +291,11 @@ def _create_updated_class( typ = _eval_types(typ, ctx) tquals = _eval_types(quals, ctx) - if type_eval.issubtype( - typing.Literal["ClassVar"], tquals - ) and _is_method_like(typ): + if ( + type_eval.issubtype(typing.Literal["ClassVar"], tquals) + and _is_method_like(typ) + and _typing_inspect.get_head(typ) is not GenericCallable + ): dct[member_name] = _callable_type_to_method(member_name, typ, ctx) else: # Update/add the annotation