@@ -47,6 +47,7 @@ class Format(enum.IntEnum): |
47 | 47 | "__cell__", |
48 | 48 | "__owner__", |
49 | 49 | "__stringifier_dict__", |
| 50 | + "__resolved_str_cache__", |
50 | 51 | ) |
51 | 52 |
|
52 | 53 |
|
@@ -94,6 +95,7 @@ def __init__( |
94 | 95 | # value later. |
95 | 96 | self.__code__ = None |
96 | 97 | self.__ast_node__ = None |
| 98 | + self.__resolved_str_cache__ = None |
97 | 99 |
|
98 | 100 | def __init_subclass__(cls, /, *args, **kwds): |
99 | 101 | raise TypeError("Cannot subclass ForwardRef") |
@@ -113,7 +115,7 @@ def evaluate( |
113 | 115 | """ |
114 | 116 | match format: |
115 | 117 | case Format.STRING: |
116 | | - return self.__forward_arg__ |
| 118 | + return self.__resolved_str__ |
117 | 119 | case Format.VALUE: |
118 | 120 | is_forwardref_format = False |
119 | 121 | case Format.FORWARDREF: |
@@ -258,6 +260,24 @@ def __forward_arg__(self): |
258 | 260 | "Attempted to access '__forward_arg__' on an uninitialized ForwardRef" |
259 | 261 | ) |
260 | 262 |
|
| 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 | + |
261 | 281 | @property |
262 | 282 | def __forward_code__(self): |
263 | 283 | if self.__code__ is not None: |
@@ -321,7 +341,7 @@ def __repr__(self): |
321 | 341 | extra.append(", is_class=True") |
322 | 342 | if self.__owner__ is not None: |
323 | 343 | 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)})" |
325 | 345 |
|
326 | 346 |
|
327 | 347 | _Template = type(t"") |
@@ -357,6 +377,7 @@ def __init__( |
357 | 377 | self.__cell__ = cell |
358 | 378 | self.__owner__ = owner |
359 | 379 | self.__stringifier_dict__ = stringifier_dict |
| 380 | + self.__resolved_str_cache__ = None # Needed for ForwardRef |
360 | 381 |
|
361 | 382 | def __convert_to_ast(self, other): |
362 | 383 | if isinstance(other, _Stringifier): |
@@ -1163,3 +1184,14 @@ def _get_dunder_annotations(obj): |
1163 | 1184 | if not isinstance(ann, dict): |
1164 | 1185 | raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None") |
1165 | 1186 | 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 |