1212)
1313from mypy .errors import Errors
1414from mypy .messages import MessageBuilder
15- from mypy .nodes import Context , Expression , StrExpr
15+ from mypy .nodes import Context , Expression
1616from mypy .options import Options
1717from mypyc .ir .ops import Integer , Value
1818from mypyc .ir .rtypes import (
2323 is_str_rprimitive ,
2424)
2525from mypyc .irbuild .builder import IRBuilder
26+ from mypyc .irbuild .constant_fold import constant_fold_expr
2627from mypyc .primitives .bytes_ops import bytes_build_op
2728from mypyc .primitives .int_ops import int_to_str_op
28- from mypyc .primitives .str_ops import str_build_op , str_op
29+ from mypyc .primitives .str_ops import ascii_op , str_build_op , str_op
2930
3031
3132@unique
@@ -41,6 +42,7 @@ class FormatOp(Enum):
4142
4243 STR = "s"
4344 INT = "d"
45+ ASCII = "a"
4446 BYTES = "b"
4547
4648
@@ -52,14 +54,25 @@ def generate_format_ops(specifiers: list[ConversionSpecifier]) -> list[FormatOp]
5254 format_ops = []
5355 for spec in specifiers :
5456 # TODO: Match specifiers instead of using whole_seq
55- if spec .whole_seq == "%s" or spec .whole_seq == "{:{}}" :
57+ # Conversion flags for str.format/f-strings (e.g. {!a}); only if no format spec.
58+ if spec .conversion and not spec .format_spec :
59+ if spec .conversion == "!a" :
60+ format_op = FormatOp .ASCII
61+ else :
62+ return None
63+ # printf-style tokens and special f-string lowering patterns.
64+ elif spec .whole_seq == "%s" or spec .whole_seq == "{:{}}" :
5665 format_op = FormatOp .STR
5766 elif spec .whole_seq == "%d" :
5867 format_op = FormatOp .INT
68+ elif spec .whole_seq == "%a" :
69+ format_op = FormatOp .ASCII
5970 elif spec .whole_seq == "%b" :
6071 format_op = FormatOp .BYTES
72+ # Any other non-empty spec means we can't optimize; fall back to runtime formatting.
6173 elif spec .whole_seq :
6274 return None
75+ # Empty spec ("{}") defaults to str().
6376 else :
6477 format_op = FormatOp .STR
6578 format_ops .append (format_op )
@@ -143,16 +156,23 @@ def convert_format_expr_to_str(
143156 for x , format_op in zip (exprs , format_ops ):
144157 node_type = builder .node_type (x )
145158 if format_op == FormatOp .STR :
146- if is_str_rprimitive ( node_type ) or isinstance (
147- x , StrExpr
148- ): # NOTE: why does mypyc think our fake StrExprs are not str rprimitives?
159+ if isinstance ( folded := constant_fold_expr ( builder , x ), str ):
160+ var_str = builder . load_literal_value ( folded )
161+ elif is_str_rprimitive ( node_type ):
149162 var_str = builder .accept (x )
150163 elif is_int_rprimitive (node_type ) or is_short_int_rprimitive (node_type ):
151164 var_str = builder .primitive_op (int_to_str_op , [builder .accept (x )], line )
152165 else :
153166 var_str = builder .primitive_op (str_op , [builder .accept (x )], line )
167+ elif format_op == FormatOp .ASCII :
168+ if (folded := constant_fold_expr (builder , x )) is not None :
169+ var_str = builder .load_literal_value (ascii (folded ))
170+ else :
171+ var_str = builder .primitive_op (ascii_op , [builder .accept (x )], line )
154172 elif format_op == FormatOp .INT :
155- if is_int_rprimitive (node_type ) or is_short_int_rprimitive (node_type ):
173+ if isinstance (folded := constant_fold_expr (builder , x ), int ):
174+ var_str = builder .load_literal_value (str (folded ))
175+ elif is_int_rprimitive (node_type ) or is_short_int_rprimitive (node_type ):
156176 var_str = builder .primitive_op (int_to_str_op , [builder .accept (x )], line )
157177 else :
158178 return None
0 commit comments