@@ -560,32 +560,81 @@ def unary_op(self):
560560 del _make_unary_op
561561
562562
563- def _template_to_ast (template ):
563+ def _conversion_ast_value (conversion ):
564+ """Convert a `conversion` character to an AST-friendly Constant."""
565+ return - 1 if conversion is None else ord (conversion )
566+
567+
568+ def _format (format_spec ):
569+ """Convert a `format_spec` string to an AST-friendly Constant."""
570+ value = format_spec or None # empty string -> None
571+ return ast .Constant (value = value )
572+
573+
574+ def _template_to_ast_constructor (template ):
575+ """Convert a `template` instance to a non-literal AST."""
576+ args = []
577+ for part in template :
578+ match part :
579+ case str ():
580+ args .append (ast .Constant (value = part ))
581+ case _:
582+ interp = ast .Call (
583+ func = ast .Name (id = "Interpolation" ),
584+ args = [
585+ ast .Constant (value = part .value ),
586+ ast .Constant (value = part .expression ),
587+ ast .Constant (value = part .conversion ),
588+ ast .Constant (value = part .format_spec ),
589+ ]
590+ )
591+ args .append (interp )
592+ return ast .Call (
593+ func = ast .Name (id = "Template" ),
594+ args = args ,
595+ keywords = [],
596+ )
597+
598+
599+ def _template_to_ast_literal (template , parsed ):
600+ """Convert a `template` instance to a t-string literal AST."""
564601 values = []
602+ interp_count = 0
565603 for part in template :
566604 match part :
567605 case str ():
568606 values .append (ast .Constant (value = part ))
569- # Interpolation, but we don't want to import the string module
570607 case _:
571608 interp = ast .Interpolation (
572609 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- ),
610+ value = parsed [interp_count ],
611+ conversion = ord (part .conversion ) if part .conversion else - 1 ,
612+ format_spec = part .format_spec or None , # "" -> None
584613 )
585614 values .append (interp )
615+ interp_count += 1
586616 return ast .TemplateStr (values = values )
587617
588618
619+ def _template_to_ast (template ):
620+ """Make a best-effort conversion of a `template` instance to an AST."""
621+ # gh-138558: Not all Template instances can be represented as t-string
622+ # literals. Return the most accurate AST we can. See issue for details.
623+ if any (part .expression == "" for part in template .interpolations ):
624+ return _template_to_ast_constructor (template )
625+
626+ try :
627+ # Wrap in parens to allow whitespace inside interpolation curly braces
628+ parsed = tuple (
629+ ast .parse (f"({ part .expression } )" , mode = "eval" ).body
630+ for part in template .interpolations
631+ )
632+ except SyntaxError :
633+ return _template_to_ast_constructor (template )
634+
635+ return _template_to_ast_literal (template , parsed )
636+
637+
589638class _StringifierDict (dict ):
590639 def __init__ (self , namespace , * , globals = None , owner = None , is_class = False , format ):
591640 super ().__init__ (namespace )
0 commit comments