11import collections
22import collections .abc
33import contextlib
4+ import dataclasses
45import functools
56import inspect
67import itertools
@@ -81,24 +82,34 @@ def _make_init_type(v):
8182 return typing .Literal [(v ,)]
8283
8384
85+ def cached_box (cls , * , ctx ):
86+ if str (cls ).startswith ('typemap.typing' ):
87+ return _apply_generic .box (cls )
88+ if cls in ctx .box_cache :
89+ return ctx .box_cache [cls ]
90+ ctx .box_cache [cls ] = box = _apply_generic .box (cls )
91+ assert box .mro
92+ # if not all(b.mro for b in box.mro):
93+ # breakpoint()
94+ # assert all(b.mro for b in box.mro)
95+
96+ if new_box := _eval_init_subclass (box , ctx ):
97+ ctx .box_cache [cls ] = box = new_box
98+ return box
99+
100+
84101def get_annotated_type_hints (cls , * , ctx , ** kwargs ):
85102 """Get the type hints/quals for a cls annotated with definition site.
86103
87104 This traverses the mro and finds the definition site for each annotation.
88105 """
89106
90- # TODO: Cache the box (slash don't need it??)
91- box = _apply_generic .box (cls )
107+ box = cached_box (cls , ctx = ctx )
92108
93109 hints = {}
94110 for abox in reversed (box .mro ):
95111 acls = abox .alias_type ()
96112
97- if abox is box and (updated_cls := _eval_init_subclass (box , ctx )):
98- # For the class itself, apply all UpdateClass from
99- # ancesstors' __init_subclass__ to get the final type.
100- abox = _apply_generic .box (updated_cls )
101-
102113 annos , _ = _apply_generic .get_local_defns (abox )
103114 for k , ty in annos .items ():
104115 quals = set ()
@@ -129,18 +140,12 @@ def get_annotated_type_hints(cls, *, ctx, **kwargs):
129140
130141
131142def get_annotated_method_hints (cls , * , ctx ):
132- # TODO: Cache the box (slash don't need it??)
133- box = _apply_generic .box (cls )
143+ box = cached_box (cls , ctx = ctx )
134144
135145 hints = {}
136146 for abox in reversed (box .mro ):
137147 acls = abox .alias_type ()
138148
139- if abox is box and (updated_cls := _eval_init_subclass (box , ctx )):
140- # For the class itself, apply all UpdateClass from
141- # ancesstors' __init_subclass__ to get the final type.
142- abox = _apply_generic .box (updated_cls )
143-
144149 _ , dct = _apply_generic .get_local_defns (abox )
145150 for name , attr in dct .items ():
146151 if isinstance (
@@ -167,25 +172,38 @@ def get_annotated_method_hints(cls, *, ctx):
167172
168173def _eval_init_subclass (
169174 box : _apply_generic .Boxed , ctx : typing .Any
170- ) -> type | None :
175+ ) -> _apply_generic . Boxed :
171176 """Get type after all __init_subclass__ with UpdateClass are evaluated."""
172- for abox in reversed (box .mro [1 :]): # Skip the type itself
173- if ms := _get_update_class_members (box .cls , abox .alias_type (), ctx = ctx ):
174- return _create_updated_class (box .cls , ms , ctx = ctx )
175-
176- return None
177+ for abox in box .mro [1 :]: # Skip the type itself
178+ with _child_context () as ctx :
179+ if ms := _get_update_class_members (
180+ box .cls , abox .alias_type (), ctx = ctx
181+ ):
182+ nbox = _apply_generic .box (
183+ _create_updated_class (box .cls , ms , ctx = ctx )
184+ )
185+ # We want to preserve the original cls for Members output
186+ box = dataclasses .replace (nbox , orig_cls = box .canonical_cls )
187+ ctx .box_cache [box .cls ] = box
188+ return box
177189
178190
179191def _get_update_class_members (
180192 cls : type , base : type , ctx : typing .Any
181193) -> list [Member ] | None :
182- if (
183- (init_subclass := base .__dict__ .get ("__init_subclass__" ))
184- # XXX: We're using get_type_hints now to evaluate hints but
185- # we should have our own generic infrastructure instead.
186- # (I'm working on it -sully)
187- and (init_subclass_annos := typing .get_type_hints (init_subclass ))
188- and (ret_annotation := init_subclass_annos .get ("return" ))
194+ init_subclass = base .__dict__ .get ("__init_subclass__" )
195+ if not init_subclass :
196+ return None
197+ init_subclass = inspect .unwrap (init_subclass )
198+
199+ args = {}
200+ if type_params := getattr (init_subclass , '__type_params__' , None ):
201+ args [str (type_params [0 ])] = cls
202+
203+ init_subclass_annos = _apply_generic .get_annotations (init_subclass , args )
204+
205+ if init_subclass_annos and (
206+ ret_annotation := init_subclass_annos .get ("return" )
189207 ):
190208 # Substitute the cls type var with the current class
191209 # This may not happen if cls is not generic!
@@ -211,12 +229,7 @@ def _get_update_class_members(
211229 )
212230
213231 # Evaluate the return annotation
214- # Do it in a child context, so the evaluations are isolated. For
215- # example, if the return annotation uses Attrs[MyClass], we want
216- # Attrs[MyClass] to be evaluated with the updated class, not the
217- # original.
218- with _child_context () as ctx :
219- evaled_ret = _eval_types (ret_annotation , ctx = ctx )
232+ evaled_ret = _eval_types (ret_annotation , ctx = ctx )
220233
221234 # If the result is an UpdateClass, return the members
222235 if (
0 commit comments