Skip to content

gh-148680: Replace internal names with type_reprs of objects in string representations of ForwardRef#148682

Open
DavidCEllis wants to merge 9 commits intopython:mainfrom
DavidCEllis:fix-forwardref-string-names
Open

gh-148680: Replace internal names with type_reprs of objects in string representations of ForwardRef#148682
DavidCEllis wants to merge 9 commits intopython:mainfrom
DavidCEllis:fix-forwardref-string-names

Conversation

@DavidCEllis
Copy link
Copy Markdown
Contributor

@DavidCEllis DavidCEllis commented Apr 17, 2026

This adds a new ForwardRef.__resolved_forward_str__ attribute which takes the __forward_arg__ and replaces any internal __annotationlib_name_x__ names with the type_repr of the object they represent.

Before:

ForwardRef('ref | __annotationlib_name_1__', is_class=True, owner=<class '__main__.Example'>)
ref | __annotationlib_name_1__

After:

ForwardRef('ref | str', is_class=True, owner=<class '__main__.Example'>)
ref | str

This approach replaces the extra_names with their type_repr when the forwardref is evaluated to a string. I'm fairly sure that trying to get the original names as they are in the source would be both slower and significantly more complicated.

This logic is largely based on something I already have for my reannotate library.

Comment thread Lib/test/test_annotationlib.py
Comment thread Lib/annotationlib.py
resolved_str = type_repr(name_obj)
else:
visitor = _ExtraNameFixer(names)
ast_expr = ast.parse(resolved_str, mode="eval").body
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to cache this, it's probably pretty slow. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is in fact, not very fast. Definitely worth caching if it's going to be accessed frequently.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

>>> a_anno
ForwardRef('unknown | str | int | list[str] | tuple[int, ...]', is_class=True, owner=<class '__main__.Example'>)
>>> b_anno
ForwardRef('unknown', is_class=True, owner=<class '__main__.Example'>)

>>> a = timeit(lambda: a_anno.__resolved_forward_str__, number=10_000)
>>> b = timeit(lambda: b_anno.__resolved_forward_str__, number=10_000)
>>> a
0.18359258100008446
>>> b
0.0018204959997092374
>>> a / b
100.8475607907994

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a cache and ended up shortening the name of the property and the cache. Adding the cache did mean adding an extra slot to both classes.

Not completely sold on the names I have if you have something better.

Comment thread Lib/annotationlib.py Outdated
Comment thread Lib/annotationlib.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants