From ad2cd9c8007407964098b6f2d133d2741403b91d Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 25 Sep 2025 23:49:19 -0400 Subject: [PATCH 1/6] [mypyc] feat: extend `get_expr_len` to try `constant_fold_expr` get_expr_len currently support StrExpr, BytesExpr, and string-type Final values constant_fold_expr already has the code to parse values from all of these, so we can deduplicate code by using it It's able to parse a few more Expressions than our existing implementation, and allows get_expr_length to automatically benefit from future constant-folding enhancements --- mypyc/irbuild/for_helpers.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 5edee6cb4df4..267232856e5f 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -65,6 +65,7 @@ short_int_rprimitive, ) from mypyc.irbuild.builder import IRBuilder +from mypyc.irbuild.constant_fold import constant_fold_expr from mypyc.irbuild.prepare import GENERATOR_HELPER_NAME from mypyc.irbuild.targets import AssignmentTarget, AssignmentTargetTuple from mypyc.primitives.dict_ops import ( @@ -1180,26 +1181,19 @@ def gen_cleanup(self) -> None: gen.gen_cleanup() -def get_expr_length(expr: Expression) -> int | None: - if isinstance(expr, (StrExpr, BytesExpr)): - return len(expr.value) +def get_expr_length(builder: IRBuilder, expr: Expression) -> int | None: + folded = constant_fold_expr(builder, expr) + if isinstance(folded, (str, bytes)): + return len(folded) elif isinstance(expr, (ListExpr, TupleExpr)): # if there are no star expressions, or we know the length of them, # we know the length of the expression - stars = [get_expr_length(i) for i in expr.items if isinstance(i, StarExpr)] + stars = [get_expr_length(builder, i) for i in expr.items if isinstance(i, StarExpr)] if None not in stars: other = sum(not isinstance(i, StarExpr) for i in expr.items) return other + sum(stars) # type: ignore [arg-type] elif isinstance(expr, StarExpr): - return get_expr_length(expr.expr) - elif ( - isinstance(expr, RefExpr) - and isinstance(expr.node, Var) - and expr.node.is_final - and isinstance(expr.node.final_value, str) - and expr.node.has_explicit_value - ): - return len(expr.node.final_value) + return get_expr_length(builder, expr.expr) # TODO: extend this, passing length of listcomp and genexp should have worthwhile # performance boost and can be (sometimes) figured out pretty easily. set and dict # comps *can* be done as well but will need special logic to consider the possibility @@ -1212,7 +1206,7 @@ def get_expr_length_value( ) -> Value: rtype = builder.node_type(expr) assert is_sequence_rprimitive(rtype), rtype - length = get_expr_length(expr) + length = get_expr_length(builder, expr) if length is None: # We cannot compute the length at compile time, so we will fetch it. return builder.builder.builtin_len(expr_reg, line, use_pyssize_t=use_pyssize_t) From f4b7b5a3b6f8f992c0e93a673d0c8ee7911c52a4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 03:51:10 +0000 Subject: [PATCH 2/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/for_helpers.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 267232856e5f..3b7ea50cdfe8 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -11,7 +11,6 @@ from mypy.nodes import ( ARG_POS, - BytesExpr, CallExpr, DictionaryComprehension, Expression, @@ -23,10 +22,8 @@ RefExpr, SetExpr, StarExpr, - StrExpr, TupleExpr, TypeAlias, - Var, ) from mypyc.ir.ops import ( ERR_NEVER, From c7b559ac62c159a16e80ddd8056fad268d6a705c Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 26 Sep 2025 00:17:45 -0400 Subject: [PATCH 3/6] Update irbuild-tuple.test --- mypyc/test-data/irbuild-tuple.test | 42 ++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 081cc1b174c9..48ea90279433 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -433,6 +433,48 @@ L4: a = r1 return 1 +[case testTupleBuiltFromConstantFolding] +def f2(val: str) -> str: + return val + "f2" + +def test() -> None: + a = tuple(f2(x) for x in "ab" + "c") + +[out] +def f2(val): + val, r0, r1 :: str +L0: + r0 = 'f2' + r1 = PyUnicode_Concat(val, r0) + return r1 +def test(): + r0 :: str + r1 :: tuple + r2 :: native_int + r3 :: bit + r4, x, r5 :: str + r6 :: native_int + a :: tuple +L0: + r0 = 'abc' + r1 = PyTuple_New(3) + r2 = 0 +L1: + r3 = r2 < 3 :: signed + if r3 goto L2 else goto L4 :: bool +L2: + r4 = CPyStr_GetItemUnsafe(r0, r2) + x = r4 + r5 = f2(x) + CPySequenceTuple_SetItemUnsafe(r1, r2, r5) +L3: + r6 = r2 + 1 + r2 = r6 + goto L1 +L4: + a = r1 + return 1 + [case testTupleBuiltFromFinalStr] from typing import Final From 93fd89f48aced860c823ffd30fbf6e8eba4e258c Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 26 Sep 2025 01:48:33 -0400 Subject: [PATCH 4/6] use a final var in the test --- mypyc/test-data/irbuild-tuple.test | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 48ea90279433..c1bd6b4f8f27 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -434,11 +434,15 @@ L4: return 1 [case testTupleBuiltFromConstantFolding] +from typing import Final + +c: Final = "c" + def f2(val: str) -> str: return val + "f2" def test() -> None: - a = tuple(f2(x) for x in "ab" + "c") + a = tuple(f2(x) for x in "ab" + c) [out] def f2(val): From a75a9c33915f0f249cfe048893eb1b631a0ed9a1 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 26 Sep 2025 07:41:06 -0400 Subject: [PATCH 5/6] add comment --- mypyc/test-data/irbuild-tuple.test | 1 + 1 file changed, 1 insertion(+) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index c1bd6b4f8f27..b5f5971294e4 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -442,6 +442,7 @@ def f2(val: str) -> str: return val + "f2" def test() -> None: + # `"ab" + c` should constant fold to "abc" a = tuple(f2(x) for x in "ab" + c) [out] From bab3dac5356ae314c0a677ad87e9152d1d993c6d Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 29 Sep 2025 11:14:20 -0400 Subject: [PATCH 6/6] Update irbuild-tuple.test --- mypyc/test-data/irbuild-tuple.test | 1 + 1 file changed, 1 insertion(+) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 1ec895720fce..b5d7f7d72a4d 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -465,6 +465,7 @@ L0: r0 = 'abc' r1 = PyTuple_New(3) r2 = 0 + goto L2 L1: r3 = r2 < 3 :: signed if r3 goto L2 else goto L4 :: bool