Skip to content

Commit 9addcad

Browse files
authored
Handle empty sequence and unpacking tuple-likes in UpdateClass. (#95)
1 parent dff3f20 commit 9addcad

File tree

2 files changed

+61
-9
lines changed

2 files changed

+61
-9
lines changed

tests/test_type_eval.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2411,6 +2411,55 @@ class D[T](C[T]):
24112411
)
24122412

24132413

2414+
def test_update_class_members_11():
2415+
class A:
2416+
a: int
2417+
2418+
def __init_subclass__[T](
2419+
cls: type[T],
2420+
) -> UpdateClass[*Members[T]]:
2421+
super().__init_subclass__()
2422+
2423+
def f(self) -> int: ...
2424+
2425+
class B(A):
2426+
b: str
2427+
2428+
def g(self) -> str: ...
2429+
2430+
attrs = eval_typing(Attrs[B])
2431+
assert (
2432+
attrs
2433+
== tuple[
2434+
Member[Literal["a"], int, Never, Never, B],
2435+
Member[Literal["b"], str, Never, Never, B],
2436+
]
2437+
)
2438+
2439+
members = eval_typing(MembersExceptInitSubclass[B])
2440+
assert (
2441+
members
2442+
== tuple[
2443+
Member[Literal["a"], int, Never, Never, B],
2444+
Member[Literal["b"], str, Never, Never, B],
2445+
Member[
2446+
Literal["f"],
2447+
Callable[[Param[Literal["self"], Self]], int],
2448+
Literal["ClassVar"],
2449+
object,
2450+
B,
2451+
],
2452+
Member[
2453+
Literal["g"],
2454+
Callable[[Param[Literal["self"], Self]], str],
2455+
Literal["ClassVar"],
2456+
object,
2457+
B,
2458+
],
2459+
]
2460+
)
2461+
2462+
24142463
def test_update_class_inheritance_01():
24152464
# current class init subclass is not applied
24162465
class A:
@@ -2528,7 +2577,6 @@ class C(B[float]):
25282577
assert eval_typing(GetArg[C, A, Literal[1]]) is float
25292578

25302579

2531-
@pytest.mark.xfail(reason="TODO")
25322580
def test_update_class_empty_01():
25332581
class A:
25342582
a: int
@@ -2542,7 +2590,7 @@ class B(A):
25422590
b: int
25432591

25442592
attrs = eval_typing(Attrs[B])
2545-
assert attrs == tuple[()]
2593+
assert attrs == tuple[Member[Literal["a"], int, Never, Never, A]]
25462594

25472595

25482596
##############

typemap/type_eval/_eval_operators.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from typemap.type_eval import _apply_generic, _typing_inspect
1616
from typemap.type_eval._eval_typing import (
1717
_child_context,
18+
_eval_args,
1819
_eval_types,
1920
EvalContext,
2021
)
@@ -192,7 +193,8 @@ def _eval_init_subclass(
192193
"""Get type after all __init_subclass__ with UpdateClass are evaluated."""
193194
for abox in box.mro[1:]: # Skip the type itself
194195
with _child_context() as ctx:
195-
if ms := _get_update_class_members(box, abox, ctx=ctx):
196+
ms = _get_update_class_members(box, abox, ctx=ctx)
197+
if ms is not None:
196198
nbox = _apply_generic.box(
197199
_create_updated_class(box, ms, ctx=ctx)
198200
)
@@ -208,7 +210,7 @@ def _get_update_class_members(
208210
box: _apply_generic.Boxed,
209211
boxed_base: _apply_generic.Boxed,
210212
ctx: EvalContext,
211-
) -> list[Member] | None:
213+
) -> typing.Sequence[Member] | None:
212214
cls = box.cls
213215

214216
# Get __init_subclass__ from the base class's origin if base is generic.
@@ -267,13 +269,13 @@ def _get_update_class_members(
267269
_typing_inspect.is_generic_alias(evaled_ret)
268270
and typing.get_origin(evaled_ret) is UpdateClass
269271
):
270-
return [m for m in typing.get_args(evaled_ret)]
272+
return _eval_args(typing.get_args(evaled_ret), ctx)
271273

272274
return None
273275

274276

275277
def _create_updated_class(
276-
box: _apply_generic.Boxed, ms: list[Member], ctx: EvalContext
278+
box: _apply_generic.Boxed, ms: typing.Sequence[Member], ctx: EvalContext
277279
) -> type:
278280
t = box.cls
279281
dct: dict[str, object] = {}
@@ -289,9 +291,11 @@ def _create_updated_class(
289291
typ = _eval_types(typ, ctx)
290292
tquals = _eval_types(quals, ctx)
291293

292-
if type_eval.issubtype(
293-
typing.Literal["ClassVar"], tquals
294-
) and _is_method_like(typ):
294+
if (
295+
type_eval.issubtype(typing.Literal["ClassVar"], tquals)
296+
and _is_method_like(typ)
297+
and _typing_inspect.get_head(typ) is not GenericCallable
298+
):
295299
dct[member_name] = _callable_type_to_method(member_name, typ, ctx)
296300
else:
297301
# Update/add the annotation

0 commit comments

Comments
 (0)