gh-138558: Improve handling of Template annotations in annotationlib (#139072)

This commit is contained in:
Dave Peck 2025-09-23 11:25:51 -07:00 committed by GitHub
parent e8382e55c5
commit 6ec058a1f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 73 additions and 14 deletions

View file

@ -560,32 +560,70 @@ def unary_op(self):
del _make_unary_op
def _template_to_ast(template):
def _template_to_ast_constructor(template):
"""Convert a `template` instance to a non-literal AST."""
args = []
for part in template:
match part:
case str():
args.append(ast.Constant(value=part))
case _:
interp = ast.Call(
func=ast.Name(id="Interpolation"),
args=[
ast.Constant(value=part.value),
ast.Constant(value=part.expression),
ast.Constant(value=part.conversion),
ast.Constant(value=part.format_spec),
]
)
args.append(interp)
return ast.Call(func=ast.Name(id="Template"), args=args, keywords=[])
def _template_to_ast_literal(template, parsed):
"""Convert a `template` instance to a t-string literal AST."""
values = []
interp_count = 0
for part in template:
match part:
case str():
values.append(ast.Constant(value=part))
# Interpolation, but we don't want to import the string module
case _:
interp = ast.Interpolation(
str=part.expression,
value=ast.parse(part.expression),
conversion=(
ord(part.conversion)
if part.conversion is not None
else -1
),
format_spec=(
ast.Constant(value=part.format_spec)
if part.format_spec != ""
else None
),
value=parsed[interp_count],
conversion=ord(part.conversion) if part.conversion else -1,
format_spec=ast.Constant(value=part.format_spec)
if part.format_spec
else None,
)
values.append(interp)
interp_count += 1
return ast.TemplateStr(values=values)
def _template_to_ast(template):
"""Make a best-effort conversion of a `template` instance to an AST."""
# gh-138558: Not all Template instances can be represented as t-string
# literals. Return the most accurate AST we can. See issue for details.
# If any expr is empty or whitespace only, we cannot convert to a literal.
if any(part.expression.strip() == "" for part in template.interpolations):
return _template_to_ast_constructor(template)
try:
# Wrap in parens to allow whitespace inside interpolation curly braces
parsed = tuple(
ast.parse(f"({part.expression})", mode="eval").body
for part in template.interpolations
)
except SyntaxError:
return _template_to_ast_constructor(template)
return _template_to_ast_literal(template, parsed)
class _StringifierDict(dict):
def __init__(self, namespace, *, globals=None, owner=None, is_class=False, format):
super().__init__(namespace)