From ad63153296730412c185b41a65347d0692c71eb8 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 25 Sep 2025 21:00:08 -0400 Subject: [PATCH 1/7] [mypyc] feat: extend get_expr_length for listcomp and genexp --- mypyc/irbuild/for_helpers.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 5edee6cb4df4..4d247decb088 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1200,10 +1200,13 @@ def get_expr_length(expr: Expression) -> int | None: and expr.node.has_explicit_value ): return len(expr.node.final_value) - # 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 - # of key conflicts. Range, enumerate, zip are all simple logic. + elif isinstance(expr, ListComprehension): + return get_expr_length(expr.generator) + elif isinstance(expr, GeneratorExpr) and len(expr.sequences) == 1 and not expr.condlists: + return get_expr_length(expr.sequences[0]) + # TODO: extend this, set and dict comps can be done as well but will + # need special logic to consider the possibility of key conflicts. + # Range, enumerate, zip are all simple logic. return None From 2328420c665feae4f463f891937b6903b7507af7 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 25 Sep 2025 21:01:59 -0400 Subject: [PATCH 2/7] remove unnecessary check --- mypyc/irbuild/for_helpers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 4d247decb088..f72b58b160a2 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1197,7 +1197,6 @@ def get_expr_length(expr: Expression) -> int | None: 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) elif isinstance(expr, ListComprehension): From f6a868fadbc54dbaa32c3f837e36bdba8eeb4a4f Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 25 Sep 2025 21:05:24 -0400 Subject: [PATCH 3/7] fix: missing import --- mypyc/irbuild/for_helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index f72b58b160a2..c4f17a9c75bc 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -16,6 +16,7 @@ DictionaryComprehension, Expression, GeneratorExpr, + ListComprehension, ListExpr, Lvalue, MemberExpr, From 2a173ff38637c77006956e9e41233a882bddbe91 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 25 Sep 2025 22:03:44 -0400 Subject: [PATCH 4/7] extend for multisequence --- mypyc/irbuild/for_helpers.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index c4f17a9c75bc..71544400d598 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1202,8 +1202,15 @@ def get_expr_length(expr: Expression) -> int | None: return len(expr.node.final_value) elif isinstance(expr, ListComprehension): return get_expr_length(expr.generator) - elif isinstance(expr, GeneratorExpr) and len(expr.sequences) == 1 and not expr.condlists: - return get_expr_length(expr.sequences[0]) + elif isinstance(expr, GeneratorExpr) and not expr.condlists: + sequence_lengths = [get_expr_length(seq) for seq in expr.sequences] + if None not in sequence_lengths: + if len(sequence_lengths) == 1: + return sequence_lengths[0] + product = sequence_lengths[0] + for l in sequence_lengths[1:]: + product *= l + return product # TODO: extend this, set and dict comps can be done as well but will # need special logic to consider the possibility of key conflicts. # Range, enumerate, zip are all simple logic. From a30208717f741576ce6ce10b6e7483ddf923e7d0 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 25 Sep 2025 22:09:19 -0400 Subject: [PATCH 5/7] fix mypy errs --- mypyc/irbuild/for_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 71544400d598..be342128c290 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1209,7 +1209,7 @@ def get_expr_length(expr: Expression) -> int | None: return sequence_lengths[0] product = sequence_lengths[0] for l in sequence_lengths[1:]: - product *= l + product *= l # type: ignore [operator] return product # TODO: extend this, set and dict comps can be done as well but will # need special logic to consider the possibility of key conflicts. From 8a8517b461e9b27789cc34b7b965ba73414420e1 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 26 Sep 2025 00:24:06 -0400 Subject: [PATCH 6/7] Update irbuild-tuple.test --- mypyc/test-data/irbuild-tuple.test | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 081cc1b174c9..5047a31d7193 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -344,6 +344,38 @@ L4: a = r6 return 1 +[case testTupleBuiltFromListComprehension] +def f(val: int) -> bool: + return val % 2 == 0 + +def test() -> None: + a = tuple(f(x) for x in [a * b for a in [1, 2, 3] for b in [1, 2, 3]]) +[out] +def f(val): + val, r0 :: int + r1 :: bit +L0: + r0 = CPyTagged_Remainder(val, 4) + r1 = int_eq r0, 0 + return r1 +def test(): + r0 :: list + r1, r2, r3 :: object + r4 :: ptr + source :: list + r5 :: native_int + r6 :: tuple + r7, r8 :: native_int + r9 :: bit + r10 :: object + r11, x :: int + r12 :: bool + r13 :: object + r14 :: native_int + a :: tuple +L0: + r6 = PyTuple_New(9) + [case testTupleBuiltFromStr] def f2(val: str) -> str: return val + "f2" From bdb20fc5611dfda55b1e1d14329e026056ee326b Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Fri, 26 Sep 2025 06:58:16 +0000 Subject: [PATCH 7/7] update ir --- mypyc/test-data/irbuild-tuple.test | 114 +++++++++++++++++++++++++---- 1 file changed, 99 insertions(+), 15 deletions(-) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 5047a31d7193..ecb36bc4b18a 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -359,22 +359,106 @@ L0: r1 = int_eq r0, 0 return r1 def test(): - r0 :: list - r1, r2, r3 :: object - r4 :: ptr - source :: list - r5 :: native_int - r6 :: tuple - r7, r8 :: native_int - r9 :: bit - r10 :: object - r11, x :: int - r12 :: bool - r13 :: object - r14 :: native_int - a :: tuple + r0, r1 :: list + r2, r3, r4 :: object + r5 :: ptr + r6, r7 :: native_int + r8 :: bit + r9 :: object + r10, a :: int + r11 :: list + r12, r13, r14 :: object + r15 :: ptr + r16, r17 :: native_int + r18 :: bit + r19 :: object + r20, b, r21 :: int + r22 :: object + r23 :: i32 + r24 :: bit + r25, r26, r27 :: native_int + r28 :: tuple + r29, r30 :: native_int + r31 :: bit + r32 :: object + r33, x :: int + r34 :: bool + r35 :: object + r36 :: native_int + a_2 :: tuple L0: - r6 = PyTuple_New(9) + r0 = PyList_New(0) + r1 = PyList_New(3) + r2 = object 1 + r3 = object 2 + r4 = object 3 + r5 = list_items r1 + buf_init_item r5, 0, r2 + buf_init_item r5, 1, r3 + buf_init_item r5, 2, r4 + keep_alive r1 + r6 = 0 +L1: + r7 = var_object_size r1 + r8 = r6 < r7 :: signed + if r8 goto L2 else goto L8 :: bool +L2: + r9 = list_get_item_unsafe r1, r6 + r10 = unbox(int, r9) + a = r10 + r11 = PyList_New(3) + r12 = object 1 + r13 = object 2 + r14 = object 3 + r15 = list_items r11 + buf_init_item r15, 0, r12 + buf_init_item r15, 1, r13 + buf_init_item r15, 2, r14 + keep_alive r11 + r16 = 0 +L3: + r17 = var_object_size r11 + r18 = r16 < r17 :: signed + if r18 goto L4 else goto L6 :: bool +L4: + r19 = list_get_item_unsafe r11, r16 + r20 = unbox(int, r19) + b = r20 + r21 = CPyTagged_Multiply(a, b) + r22 = box(int, r21) + r23 = PyList_Append(r0, r22) + r24 = r23 >= 0 :: signed +L5: + r25 = r16 + 1 + r16 = r25 + goto L3 +L6: +L7: + r26 = r6 + 1 + r6 = r26 + goto L1 +L8: + r27 = var_object_size r0 + r28 = PyTuple_New(r27) + r29 = 0 +L9: + r30 = var_object_size r0 + r31 = r29 < r30 :: signed + if r31 goto L10 else goto L12 :: bool +L10: + r32 = list_get_item_unsafe r0, r29 + r33 = unbox(int, r32) + x = r33 + r34 = f(x) + r35 = box(bool, r34) + CPySequenceTuple_SetItemUnsafe(r28, r29, r35) +L11: + r36 = r29 + 1 + r29 = r36 + goto L9 +L12: + a_2 = r28 + return 1 [case testTupleBuiltFromStr] def f2(val: str) -> str: