@@ -560,32 +560,70 @@ def unary_op(self):
560
560
del _make_unary_op
561
561
562
562
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."""
564
586
values = []
587
+ interp_count = 0
565
588
for part in template :
566
589
match part :
567
590
case str ():
568
591
values .append (ast .Constant (value = part ))
569
- # Interpolation, but we don't want to import the string module
570
592
case _:
571
593
interp = ast .Interpolation (
572
594
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 ,
584
600
)
585
601
values .append (interp )
602
+ interp_count += 1
586
603
return ast .TemplateStr (values = values )
587
604
588
605
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
+
589
627
class _StringifierDict (dict ):
590
628
def __init__ (self , namespace , * , globals = None , owner = None , is_class = False , format ):
591
629
super ().__init__ (namespace )
0 commit comments