gh-148680: Replace internal names with type_reprs of objects in string representations of ForwardRef#148682
gh-148680: Replace internal names with type_reprs of objects in string representations of ForwardRef#148682DavidCEllis wants to merge 4 commits intopython:mainfrom
Conversation
| "typing.List[ForwardRef('int', owner='class')]", | ||
| ) | ||
|
|
||
| def test_forward_repr_extra_names(self): |
There was a problem hiding this comment.
Can you make the tests rely less on direct assignment to __extra_names__? I don't care too much about the behavior if you manually construct ForwardRefs and set attributes on them, what matters is the buggy behavior with things you get from get_annotations.
There was a problem hiding this comment.
Yes, I can do that here. This was partly done to avoid the owner=... part of the repr.
The other example of this in test_evaluate_string_format_extra_names exists largely to exercise the logic for the single identifier case. I'm actually not sure you can get the single identifier case with standard get_annotations?
I need this in reannotate for splitting up unions within forward references but unless you're doing similar manipulations I'm not sure this is a case you can get?
Perhaps the isidentifier special case should be removed? The other logic should work for it anyway.
| resolved_str = type_repr(name_obj) | ||
| else: | ||
| visitor = _ExtraNameFixer(names) | ||
| ast_expr = ast.parse(resolved_str, mode="eval").body |
There was a problem hiding this comment.
We might need to cache this, it's probably pretty slow. What do you think?
There was a problem hiding this comment.
This is in fact, not very fast. Definitely worth caching if it's going to be accessed frequently.
There was a problem hiding this comment.
>>> 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
This adds a new
ForwardRef.__resolved_forward_str__attribute which takes the__forward_arg__and replaces any internal__annotationlib_name_x__names with thetype_reprof the object they represent.Before:
After:
This approach replaces the extra_names with their
type_reprwhen 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
reannotatelibrary.