From 8750faa8fb93fff82835139173647276dcfec9a3 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Wed, 1 Oct 2025 23:53:15 +0000 Subject: [PATCH 1/8] [mypyc] feat: support constant folding in `convert_format_expr_to_str` --- mypyc/irbuild/format_str_tokenizer.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mypyc/irbuild/format_str_tokenizer.py b/mypyc/irbuild/format_str_tokenizer.py index 5a35900006d2..c7560bc87bab 100644 --- a/mypyc/irbuild/format_str_tokenizer.py +++ b/mypyc/irbuild/format_str_tokenizer.py @@ -23,6 +23,7 @@ is_str_rprimitive, ) from mypyc.irbuild.builder import IRBuilder +from mypyc.irbuild.constant_fold import constant_fold_expr from mypyc.primitives.bytes_ops import bytes_build_op from mypyc.primitives.int_ops import int_to_str_op from mypyc.primitives.str_ops import str_build_op, str_op @@ -143,16 +144,18 @@ def convert_format_expr_to_str( for x, format_op in zip(exprs, format_ops): node_type = builder.node_type(x) if format_op == FormatOp.STR: - if is_str_rprimitive(node_type) or isinstance( - x, StrExpr - ): # NOTE: why does mypyc think our fake StrExprs are not str rprimitives? + if isinstance(folded := constant_fold_expr(builder, x), str): + var_str = builder.load_literal_value(folded) + elif is_str_rprimitive(node_type): var_str = builder.accept(x) elif is_int_rprimitive(node_type) or is_short_int_rprimitive(node_type): var_str = builder.primitive_op(int_to_str_op, [builder.accept(x)], line) else: var_str = builder.primitive_op(str_op, [builder.accept(x)], line) elif format_op == FormatOp.INT: - if is_int_rprimitive(node_type) or is_short_int_rprimitive(node_type): + if isinstance(folded := constant_fold_expr(builder, x), int): + var_str = builder.load_literal_value(str(folded)) + elif is_int_rprimitive(node_type) or is_short_int_rprimitive(node_type): var_str = builder.primitive_op(int_to_str_op, [builder.accept(x)], line) else: return None From 5bc0c10c43eb0309b2f56106a777a2213d88a4c7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 00:08:19 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/format_str_tokenizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/irbuild/format_str_tokenizer.py b/mypyc/irbuild/format_str_tokenizer.py index c7560bc87bab..2596e8e050a6 100644 --- a/mypyc/irbuild/format_str_tokenizer.py +++ b/mypyc/irbuild/format_str_tokenizer.py @@ -12,7 +12,7 @@ ) from mypy.errors import Errors from mypy.messages import MessageBuilder -from mypy.nodes import Context, Expression, StrExpr +from mypy.nodes import Context, Expression from mypy.options import Options from mypyc.ir.ops import Integer, Value from mypyc.ir.rtypes import ( From 4f9ddf6507d64241c0e32a167eb608a73e4cfa1b Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 6 Jan 2026 09:56:16 +0000 Subject: [PATCH 3/8] irtest --- mypyc/test-data/irbuild-constant-fold.test | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/mypyc/test-data/irbuild-constant-fold.test b/mypyc/test-data/irbuild-constant-fold.test index cd953c84c541..06831043ff10 100644 --- a/mypyc/test-data/irbuild-constant-fold.test +++ b/mypyc/test-data/irbuild-constant-fold.test @@ -186,6 +186,25 @@ L0: big5 = r4 return 1 +[case testConstantFoldFormatArgs] +# This only tests that the callee and args are constant folded, +# it is not intended to test the result. +from typing import Any, Final + +FMT: Final = "{:d} {}" + +def f() -> str: + return FMT.format(400 + 20, "roll" + "up") +[out] +def f(): + r0, r1, r2, r3 :: str +L0: + r0 = '420' + r1 = ' ' + r2 = 'rollup' + r3 = CPyStr_Build(3, r0, r1, r2) + return r3 + [case testIntConstantFoldingFinal] from typing import Final X: Final = 5 From 482624457c520362bf524de41fa1c8efa8bbb91d Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 6 Jan 2026 10:42:26 +0000 Subject: [PATCH 4/8] fix test --- mypyc/test-data/irbuild-constant-fold.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/test-data/irbuild-constant-fold.test b/mypyc/test-data/irbuild-constant-fold.test index 06831043ff10..803ce3b4d549 100644 --- a/mypyc/test-data/irbuild-constant-fold.test +++ b/mypyc/test-data/irbuild-constant-fold.test @@ -191,7 +191,7 @@ L0: # it is not intended to test the result. from typing import Any, Final -FMT: Final = "{:d} {}" +FMT: Final = "{} {}" def f() -> str: return FMT.format(400 + 20, "roll" + "up") From f07478a870480f651c6352d74db0f8c219d382aa Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 6 Jan 2026 11:02:46 +0000 Subject: [PATCH 5/8] fix test --- mypyc/test-data/irbuild-constant-fold.test | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypyc/test-data/irbuild-constant-fold.test b/mypyc/test-data/irbuild-constant-fold.test index 803ce3b4d549..009a4e68f678 100644 --- a/mypyc/test-data/irbuild-constant-fold.test +++ b/mypyc/test-data/irbuild-constant-fold.test @@ -199,10 +199,10 @@ def f() -> str: def f(): r0, r1, r2, r3 :: str L0: - r0 = '420' - r1 = ' ' - r2 = 'rollup' - r3 = CPyStr_Build(3, r0, r1, r2) + r0 = CPyTagged_Str(840) + r1 = 'rollup' + r2 = ' ' + r3 = CPyStr_Build(3, r0, r2, r1) return r3 [case testIntConstantFoldingFinal] From 174af4aef25503386b559145e72781ee7956ba7f Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 6 Jan 2026 11:11:16 +0000 Subject: [PATCH 6/8] [mypyc]: support :s and :d in generate_format_ops --- mypyc/irbuild/format_str_tokenizer.py | 4 ++-- mypyc/test-data/irbuild-constant-fold.test | 26 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/format_str_tokenizer.py b/mypyc/irbuild/format_str_tokenizer.py index 2596e8e050a6..2a4212b55a6d 100644 --- a/mypyc/irbuild/format_str_tokenizer.py +++ b/mypyc/irbuild/format_str_tokenizer.py @@ -53,9 +53,9 @@ def generate_format_ops(specifiers: list[ConversionSpecifier]) -> list[FormatOp] format_ops = [] for spec in specifiers: # TODO: Match specifiers instead of using whole_seq - if spec.whole_seq == "%s" or spec.whole_seq == "{:{}}": + if spec.whole_seq == "%s" or spec.whole_seq in ("{:{}}", ":s"): format_op = FormatOp.STR - elif spec.whole_seq == "%d": + elif spec.whole_seq in ("%d", ":d"): format_op = FormatOp.INT elif spec.whole_seq == "%b": format_op = FormatOp.BYTES diff --git a/mypyc/test-data/irbuild-constant-fold.test b/mypyc/test-data/irbuild-constant-fold.test index 009a4e68f678..43a6567e3aae 100644 --- a/mypyc/test-data/irbuild-constant-fold.test +++ b/mypyc/test-data/irbuild-constant-fold.test @@ -192,6 +192,8 @@ L0: from typing import Any, Final FMT: Final = "{} {}" +FMT_D: Final = "{:d} {:s}" +FMT_S: Final = "{:s} {:s}" def f() -> str: return FMT.format(400 + 20, "roll" + "up") @@ -205,6 +207,30 @@ L0: r3 = CPyStr_Build(3, r0, r2, r1) return r3 +def g() -> str: + return FMT_D.format(400 + 20, "roll" + "up") +[out] +def g(): + r0, r1, r2, r3 :: str +L0: + r0 = '420' + r1 = ' ' + r2 = 'rollup' + r3 = CPyStr_Build(3, r0, r1, r2) + return r3 + +def h() -> str: + return FMT_S.format(400 + 20, "roll" + "up") +[out] +def h(): + r0, r1, r2, r3 :: str +L0: + r0 = '420' + r1 = ' ' + r2 = 'rollup' + r3 = CPyStr_Build(3, r0, r1, r2) + return r3 + [case testIntConstantFoldingFinal] from typing import Final X: Final = 5 From 4d7a3bcd40a62fd654ad1ac17f03e110893bafc4 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 6 Jan 2026 11:12:18 +0000 Subject: [PATCH 7/8] fix test --- mypyc/test-data/irbuild-constant-fold.test | 27 +++++++++++----------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/mypyc/test-data/irbuild-constant-fold.test b/mypyc/test-data/irbuild-constant-fold.test index 43a6567e3aae..9bdfca4a6713 100644 --- a/mypyc/test-data/irbuild-constant-fold.test +++ b/mypyc/test-data/irbuild-constant-fold.test @@ -197,6 +197,11 @@ FMT_S: Final = "{:s} {:s}" def f() -> str: return FMT.format(400 + 20, "roll" + "up") +def g() -> str: + return FMT_D.format(400 + 20, "roll" + "up") +def h() -> str: + return FMT_S.format(400 + 20, "roll" + "up") + [out] def f(): r0, r1, r2, r3 :: str @@ -207,28 +212,22 @@ L0: r3 = CPyStr_Build(3, r0, r2, r1) return r3 -def g() -> str: - return FMT_D.format(400 + 20, "roll" + "up") -[out] def g(): r0, r1, r2, r3 :: str L0: - r0 = '420' - r1 = ' ' - r2 = 'rollup' - r3 = CPyStr_Build(3, r0, r1, r2) + r0 = CPyTagged_Str(840) + r1 = 'rollup' + r2 = ' ' + r3 = CPyStr_Build(3, r0, r2, r1) return r3 -def h() -> str: - return FMT_S.format(400 + 20, "roll" + "up") -[out] def h(): r0, r1, r2, r3 :: str L0: - r0 = '420' - r1 = ' ' - r2 = 'rollup' - r3 = CPyStr_Build(3, r0, r1, r2) + r0 = CPyTagged_Str(840) + r1 = 'rollup' + r2 = ' ' + r3 = CPyStr_Build(3, r0, r2, r1) return r3 [case testIntConstantFoldingFinal] From 6edb7a78b12babcf5a4de9f1a8efec63b330896d Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:09:02 -0400 Subject: [PATCH 8/8] Update irbuild-constant-fold.test --- mypyc/test-data/irbuild-constant-fold.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/test-data/irbuild-constant-fold.test b/mypyc/test-data/irbuild-constant-fold.test index 9bdfca4a6713..01b5f3cf8484 100644 --- a/mypyc/test-data/irbuild-constant-fold.test +++ b/mypyc/test-data/irbuild-constant-fold.test @@ -215,7 +215,7 @@ L0: def g(): r0, r1, r2, r3 :: str L0: - r0 = CPyTagged_Str(840) + r0 = '420' r1 = 'rollup' r2 = ' ' r3 = CPyStr_Build(3, r0, r2, r1)