@@ -560,32 +560,70 @@ def unary_op(self):
560560 del _make_unary_op
561561
562562
563- def _template_to_ast (template ):
563+ def _template_to_ast_constructor (template ):
564+ """Convert a `template` instance to a non-literal AST."""
565+ args = []
566+ for part in template :
567+ match part :
568+ case str ():
569+ args .append (ast .Constant (value = part ))
570+ case _:
571+ interp = ast .Call (
572+ func = ast .Name (id = "Interpolation" ),
573+ args = [
574+ ast .Constant (value = part .value ),
575+ ast .Constant (value = part .expression ),
576+ ast .Constant (value = part .conversion ),
577+ ast .Constant (value = part .format_spec ),
578+ ]
579+ )
580+ args .append (interp )
581+ return ast .Call (func = ast .Name (id = "Template" ), args = args , keywords = [])
582+
583+
584+ def _template_to_ast_literal (template , parsed ):
585+ """Convert a `template` instance to a t-string literal AST."""
564586 values = []
587+ interp_count = 0
565588 for part in template :
566589 match part :
567590 case str ():
568591 values .append (ast .Constant (value = part ))
569- # Interpolation, but we don't want to import the string module
570592 case _:
571593 interp = ast .Interpolation (
572594 str = part .expression ,
573- value = ast .parse (part .expression ),
574- conversion = (
575- ord (part .conversion )
576- if part .conversion is not None
577- else - 1
578- ),
579- format_spec = (
580- ast .Constant (value = part .format_spec )
581- if part .format_spec != ""
582- else None
583- ),
595+ value = parsed [interp_count ],
596+ conversion = ord (part .conversion ) if part .conversion else - 1 ,
597+ format_spec = ast .Constant (value = part .format_spec )
598+ if part .format_spec
599+ else None ,
584600 )
585601 values .append (interp )
602+ interp_count += 1
586603 return ast .TemplateStr (values = values )
587604
588605
606+ def _template_to_ast (template ):
607+ """Make a best-effort conversion of a `template` instance to an AST."""
608+ # gh-138558: Not all Template instances can be represented as t-string
609+ # literals. Return the most accurate AST we can. See issue for details.
610+
611+ # If any expr is empty or whitespace only, we cannot convert to a literal.
612+ if any (part .expression .strip () == "" for part in template .interpolations ):
613+ return _template_to_ast_constructor (template )
614+
615+ try :
616+ # Wrap in parens to allow whitespace inside interpolation curly braces
617+ parsed = tuple (
618+ ast .parse (f"({ part .expression } )" , mode = "eval" ).body
619+ for part in template .interpolations
620+ )
621+ except SyntaxError :
622+ return _template_to_ast_constructor (template )
623+
624+ return _template_to_ast_literal (template , parsed )
625+
626+
589627class _StringifierDict (dict ):
590628 def __init__ (self , namespace , * , globals = None , owner = None , is_class = False , format ):
591629 super ().__init__ (namespace )
0 commit comments