@@ -47,6 +47,7 @@ class Format(enum.IntEnum):
4747 "__cell__" ,
4848 "__owner__" ,
4949 "__stringifier_dict__" ,
50+ "__resolved_str_cache__" ,
5051)
5152
5253
@@ -94,6 +95,7 @@ def __init__(
9495 # value later.
9596 self .__code__ = None
9697 self .__ast_node__ = None
98+ self .__resolved_str_cache__ = None
9799
98100 def __init_subclass__ (cls , / , * args , ** kwds ):
99101 raise TypeError ("Cannot subclass ForwardRef" )
@@ -113,7 +115,7 @@ def evaluate(
113115 """
114116 match format :
115117 case Format .STRING :
116- return self .__forward_arg__
118+ return self .__resolved_str__
117119 case Format .VALUE :
118120 is_forwardref_format = False
119121 case Format .FORWARDREF :
@@ -258,6 +260,24 @@ def __forward_arg__(self):
258260 "Attempted to access '__forward_arg__' on an uninitialized ForwardRef"
259261 )
260262
263+ @property
264+ def __resolved_str__ (self ):
265+ # __forward_arg__ with any names from __extra_names__ replaced
266+ # with the type_repr of the value they represent
267+ if self .__resolved_str_cache__ is None :
268+ resolved_str = self .__forward_arg__
269+ names = self .__extra_names__
270+
271+ if names :
272+ visitor = _ExtraNameFixer (names )
273+ ast_expr = ast .parse (resolved_str , mode = "eval" ).body
274+ node = visitor .visit (ast_expr )
275+ resolved_str = ast .unparse (node )
276+
277+ self .__resolved_str_cache__ = resolved_str
278+
279+ return self .__resolved_str_cache__
280+
261281 @property
262282 def __forward_code__ (self ):
263283 if self .__code__ is not None :
@@ -321,7 +341,7 @@ def __repr__(self):
321341 extra .append (", is_class=True" )
322342 if self .__owner__ is not None :
323343 extra .append (f", owner={ self .__owner__ !r} " )
324- return f"ForwardRef({ self .__forward_arg__ !r} { '' .join (extra )} )"
344+ return f"ForwardRef({ self .__resolved_str__ !r} { '' .join (extra )} )"
325345
326346
327347_Template = type (t "" )
@@ -357,6 +377,7 @@ def __init__(
357377 self .__cell__ = cell
358378 self .__owner__ = owner
359379 self .__stringifier_dict__ = stringifier_dict
380+ self .__resolved_str_cache__ = None # Needed for ForwardRef
360381
361382 def __convert_to_ast (self , other ):
362383 if isinstance (other , _Stringifier ):
@@ -1163,3 +1184,14 @@ def _get_dunder_annotations(obj):
11631184 if not isinstance (ann , dict ):
11641185 raise ValueError (f"{ obj !r} .__annotations__ is neither a dict nor None" )
11651186 return ann
1187+
1188+
1189+ class _ExtraNameFixer (ast .NodeTransformer ):
1190+ """Fixer for __extra_names__ items in ForwardRef __repr__ and string evaluation"""
1191+ def __init__ (self , extra_names ):
1192+ self .extra_names = extra_names
1193+
1194+ def visit_Name (self , node : ast .Name ):
1195+ if (new_name := self .extra_names .get (node .id , _sentinel )) is not _sentinel :
1196+ node = ast .Name (id = type_repr (new_name ))
1197+ return node
0 commit comments