Skip to content

Commit e1e258e

Browse files
committed
Replace names from __extra_names__ when evaluating forward references as strings
1 parent 12d7a45 commit e1e258e

File tree

1 file changed

+34
-2
lines changed

1 file changed

+34
-2
lines changed

Lib/annotationlib.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def evaluate(
113113
"""
114114
match format:
115115
case Format.STRING:
116-
return self.__forward_arg__
116+
return self.__resolved_forward_str__
117117
case Format.VALUE:
118118
is_forwardref_format = False
119119
case Format.FORWARDREF:
@@ -258,6 +258,25 @@ def __forward_arg__(self):
258258
"Attempted to access '__forward_arg__' on an uninitialized ForwardRef"
259259
)
260260

261+
@property
262+
def __resolved_forward_str__(self):
263+
# __forward_arg__ but with __extra_names__ resolved as strings
264+
resolved_str = self.__forward_arg__
265+
names = self.__extra_names__
266+
267+
if names:
268+
# identifiers can be replaced directly
269+
if resolved_str.isidentifier():
270+
if (name_obj := names.get(resolved_str), _sentinel) is not _sentinel:
271+
resolved_str = type_repr(name_obj)
272+
else:
273+
visitor = _ExtraNameFixer(names)
274+
ast_expr = ast.parse(resolved_str, mode="eval").body
275+
node = visitor.visit(ast_expr)
276+
resolved_str = ast.unparse(node)
277+
278+
return resolved_str
279+
261280
@property
262281
def __forward_code__(self):
263282
if self.__code__ is not None:
@@ -321,7 +340,7 @@ def __repr__(self):
321340
extra.append(", is_class=True")
322341
if self.__owner__ is not None:
323342
extra.append(f", owner={self.__owner__!r}")
324-
return f"ForwardRef({self.__forward_arg__!r}{''.join(extra)})"
343+
return f"ForwardRef({self.__resolved_forward_str__!r}{''.join(extra)})"
325344

326345

327346
_Template = type(t"")
@@ -1163,3 +1182,16 @@ def _get_dunder_annotations(obj):
11631182
if not isinstance(ann, dict):
11641183
raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
11651184
return ann
1185+
1186+
1187+
class _ExtraNameFixer(ast.NodeTransformer):
1188+
"""Fixer for __extra_names__ items in ForwardRef __repr__ and string evaluation"""
1189+
def __init__(self, extra_names):
1190+
self.extra_names = extra_names
1191+
1192+
def visit_Name(self, node: ast.Name):
1193+
if (new_name := self.extra_names.get(node.id, _sentinel)) is not _sentinel:
1194+
new_node = ast.Name(id=type_repr(new_name))
1195+
ast.copy_location(node, new_node)
1196+
node = new_node
1197+
return node

0 commit comments

Comments
 (0)