From 126f300ee2d5928ceb33c663634ddc5b08341b97 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 28 Jul 2025 00:56:21 +0000 Subject: [PATCH 01/16] working tuple stuff --- mypyc/irbuild/for_helpers.py | 110 ++++++++++++++++++++++++++++- mypyc/irbuild/ll_builder.py | 2 +- mypyc/test-data/irbuild-basic.test | 94 ++++++++++++++++++++++++ mypyc/test-data/irbuild-set.test | 16 +++-- test-data/unit/pythoneval.test | 4 +- test-data/unit/stubgen.test | 4 +- 6 files changed, 220 insertions(+), 10 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 5cf89f579ec4..0c58725eb2bc 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -20,6 +20,7 @@ NameExpr, RefExpr, SetExpr, + StrExpr, TupleExpr, TypeAlias, ) @@ -112,9 +113,16 @@ def for_loop_helper( normal_loop_exit = else_block if else_insts is not None else exit_block for_gen = make_for_loop_generator( - builder, index, expr, body_block, normal_loop_exit, line, is_async=is_async + builder, index, expr, body_block, normal_loop_exit, line, is_async=is_async, body_insts=body_insts ) + is_literal_loop: bool = getattr(for_gen, "handles_body_insts", False) + + # Only call body_insts if not handled by unrolled generator + if is_literal_loop: + for_gen.begin_body() + return + builder.push_loop_stack(step_block, exit_block) condition_block = BasicBlock() builder.goto_and_activate(condition_block) @@ -386,6 +394,7 @@ def make_for_loop_generator( line: int, is_async: bool = False, nested: bool = False, + body_insts: GenFunc = None, ) -> ForGenerator: """Return helper object for generating a for loop over an iterable. @@ -402,6 +411,23 @@ def make_for_loop_generator( return async_obj rtyp = builder.node_type(expr) + + # Special case: tuple literal (unroll the loop) + if isinstance(expr, TupleExpr): + return ForUnrolledLiteral(builder, index, body_block, loop_exit, line, expr.items, expr, body_insts) + + # Special case: RTuple (known-length tuple, index-based iteration) + if isinstance(rtyp, RTuple): + expr_reg = builder.accept(expr) + target_type = builder.get_sequence_type(expr) + for_tuple = ForSequence(builder, index, body_block, loop_exit, line, nested) + for_tuple.init(expr_reg, target_type, reverse=False) + return for_tuple + + # Special case: string literal (unroll the loop) + if isinstance(expr, StrExpr): + return ForUnrolledStringLiteral(builder, index, body_block, loop_exit, line, expr.value, expr, body_insts) + if is_sequence_rprimitive(rtyp): # Special case "for x in ". expr_reg = builder.accept(expr) @@ -764,6 +790,88 @@ def gen_step(self) -> None: pass +class ForUnrolledLiteral(ForGenerator): + """Generate IR for a for loop over a tuple literal by unrolling the loop. + + This class emits the loop body for each element of the tuple literal directly, + avoiding any runtime iteration logic. + """ + handles_body_insts = True + + def __init__( + self, + builder: IRBuilder, + index: Lvalue, + body_block: BasicBlock, + loop_exit: BasicBlock, + line: int, + items: list[Expression], + expr: Expression, + body_insts: GenFunc, + ) -> None: + super().__init__(builder, index, body_block, loop_exit, line, nested=False) + self.items = items + self.expr = expr + self.body_insts = body_insts + + def gen_condition(self) -> None: + # Unrolled: nothing to do here. + pass + + def begin_body(self) -> None: + builder = self.builder + for item in self.items: + builder.assign(builder.get_assignment_target(self.index), builder.accept(item), self.line) + self.body_insts() + + def gen_step(self) -> None: + # Unrolled: nothing to do here. + pass + + +class ForUnrolledStringLiteral(ForGenerator): + """Generate IR for a for loop over a string literal by unrolling the loop. + + This class emits the loop body for each character of the string literal directly, + avoiding any runtime iteration logic. + """ + handles_body_insts = True + + def __init__( + self, + builder: IRBuilder, + index: Lvalue, + body_block: BasicBlock, + loop_exit: BasicBlock, + line: int, + value: str, + expr: Expression, + body_insts: GenFunc, + ) -> None: + super().__init__(builder, index, body_block, loop_exit, line, nested=False) + self.value = value + self.expr = expr + self.body_insts = body_insts + + def gen_condition(self) -> None: + # Unrolled: nothing to do here. + pass + + def begin_body(self) -> None: + builder = self.builder + for c in self.value: + builder.assign( + builder.get_assignment_target(self.index), + builder.accept(StrExpr(c)), + self.line, + ) + self.body_insts() + + def gen_step(self) -> None: + # Unrolled: nothing to do here. + pass + + def unsafe_index(builder: IRBuilder, target: Value, index: Value, line: int) -> Value: """Emit a potentially unsafe index into a target.""" # This doesn't really fit nicely into any of our data-driven frameworks diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 79ad4cc62822..6a9a13b2570b 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -273,7 +273,7 @@ def goto(self, target: BasicBlock) -> None: def activate_block(self, block: BasicBlock) -> None: """Add a basic block and make it the active one (target of adds).""" if self.blocks: - assert self.blocks[-1].terminated + assert self.blocks[-1].terminated, self.blocks[-1] block.error_handler = self.error_handlers[-1] self.blocks.append(block) diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 4a7d315ec836..da7b01da0ba9 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -3546,3 +3546,97 @@ L0: r2 = PyObject_Vectorcall(r1, 0, 0, 0) r3 = box(None, 1) return r3 + +[case testForOverTupleLiteral] +def f() -> int: + s = 0 + for x in (1, 2, 3): + s += x + return s +[out] +def f(): + s, x, r0, r1, r2 :: int +L0: + s = 0 + x = 2 + r0 = CPyTagged_Add(s, x) + s = r0 + x = 4 + r1 = CPyTagged_Add(s, x) + s = r1 + x = 6 + r2 = CPyTagged_Add(s, x) + s = r2 + return s + +[case testForOverStringLiteral] +def f() -> str: + out = "" + for c in "abc": + out += c + return out +[out] +def f(): + r0, out, r1, c, r2, r3, r4, r5, r6 :: str +L0: + r0 = '' + out = r0 + r1 = 'a' + c = r1 + r2 = CPyStr_Append(out, c) + out = r2 + r3 = 'b' + c = r3 + r4 = CPyStr_Append(out, c) + out = r4 + r5 = 'c' + c = r5 + r6 = CPyStr_Append(out, c) + out = r6 + return out + +[case testForOverRTuple] +from typing import Tuple +def f(t: Tuple[int, int]) -> int: + s = 0 + for x in t: + s += x + return s +[out] +def f(t): + t :: tuple[int, int] + s, x :: int + r0 :: int + s = 0 + x = t.f0 + L1: + s = CPyTagged_Add(s, x) + x = t.f1 + L2: + s = CPyTagged_Add(s, x) + return s + +[case testForOverStringVar] +def f(s: str) -> str: + out = "" + for c in s: + out += c + return out +[out] +def f(s): + s :: str + out, c :: str + r0 :: int + L0: + out = '' + r0 = 0 + L1: + r1 = r0 < CPyStr_Size(s) + if r1 goto L2 else goto L4 :: bool + L2: + c = CPyStr_GetItemUnsafe(s, r0) + out = CPyStr_Append(out, c) + r0 = r0 + 1 + goto L1 + L4: + return out diff --git a/mypyc/test-data/irbuild-set.test b/mypyc/test-data/irbuild-set.test index 5586a2bf4cfb..190ecb998198 100644 --- a/mypyc/test-data/irbuild-set.test +++ b/mypyc/test-data/irbuild-set.test @@ -129,11 +129,19 @@ L4: def test2(): r0, tmp_tuple :: tuple[int, int, int] r1 :: set - r2, r3, r4 :: object - r5, x, r6 :: int + r2 :: native_int + r3 :: object + r4 :: native_int + r5, r6 :: bit r7 :: object - r8 :: i32 - r9, r10 :: bit + r8 :: bit + r9, r10, r12 :: int + r13 :: object + r14, x, r15 :: int + r16 :: object + r17 :: i32 + r18 :: bit + r19 :: native_int b :: set L0: r0 = (2, 6, 10) diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 72c00a3b9b1c..74f95286c712 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1131,9 +1131,9 @@ async def main() -> None: reveal_type(a_y) reveal_type(asyncio.gather(*[asyncio.sleep(1), asyncio.sleep(1)])) [out] -_testAsyncioGatherPreciseType.py:9: note: Revealed type is "builtins.str" _testAsyncioGatherPreciseType.py:10: note: Revealed type is "builtins.str" -_testAsyncioGatherPreciseType.py:11: note: Revealed type is "asyncio.futures.Future[builtins.list[Any]]" +_testAsyncioGatherPreciseType.py:11: note: Revealed type is "builtins.str" +_testAsyncioGatherPreciseType.py:12: note: Revealed type is "asyncio.futures.Future[builtins.list[None]]" [case testMultipleInheritanceWorksWithTupleTypeGeneric] from typing import SupportsAbs, NamedTuple diff --git a/test-data/unit/stubgen.test b/test-data/unit/stubgen.test index 161f14e8aea7..48ed600b864c 100644 --- a/test-data/unit/stubgen.test +++ b/test-data/unit/stubgen.test @@ -988,14 +988,14 @@ class RegularClass: from typing import NamedTuple # TODO: make sure that nested classes in `NamedTuple` are supported: -class NamedTupleWithNestedClass(NamedTuple): +class NamedTupleWithNestedClass(NamedTuple): ... class Nested: x: int y: str = 'a' [out] from typing import NamedTuple -class NamedTupleWithNestedClass(NamedTuple): +class NamedTupleWithNestedClass(NamedTuple): ... class Nested: x: int y: str From c3df28bb902eabb261dd8f87111989b7edfaff6c Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 28 Jul 2025 00:56:21 +0000 Subject: [PATCH 02/16] fix: rtuple and feat: general literal sequence --- mypyc/irbuild/for_helpers.py | 162 +++++++++++++++++++++++------ mypyc/test-data/irbuild-basic.test | 27 +++-- mypyc/test-data/irbuild-set.test | 84 +++++++-------- 3 files changed, 188 insertions(+), 85 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 0c58725eb2bc..1a2332a5b502 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -7,14 +7,18 @@ from __future__ import annotations -from typing import Callable, ClassVar +from typing import Any, Callable, ClassVar, Union from mypy.nodes import ( ARG_POS, + BytesExpr, CallExpr, DictionaryComprehension, Expression, + FloatExpr, GeneratorExpr, + IntExpr, + ListExpr, Lvalue, MemberExpr, NameExpr, @@ -385,6 +389,27 @@ def is_range_ref(expr: RefExpr) -> bool: ) +def is_literal_expr(expr: Expression) -> bool: + # Add other literal types as needed + if isinstance(expr, (IntExpr, StrExpr, FloatExpr, BytesExpr)): + return True + if isinstance(expr, NameExpr) and expr.fullname in {"builtins.None", "builtins.True", "builtins.False"}: + return True + return False + + +def is_iterable_expr_with_literal_mambers(expr: Expression) -> bool: + return ( + isinstance(expr, (ListExpr, SetExpr, TupleExpr)) + and not isinstance(expr, MemberExpr) + and all( + is_literal_expr(item) + or is_iterable_expr_with_literal_mambers(item) + for item in expr.items + ) + ) + + def make_for_loop_generator( builder: IRBuilder, index: Lvalue, @@ -413,21 +438,22 @@ def make_for_loop_generator( rtyp = builder.node_type(expr) # Special case: tuple literal (unroll the loop) - if isinstance(expr, TupleExpr): - return ForUnrolledLiteral(builder, index, body_block, loop_exit, line, expr.items, expr, body_insts) + if is_iterable_expr_with_literal_mambers(expr): + return ForUnrolledSequenceLiteral(builder, index, body_block, loop_exit, line, expr, body_insts) - # Special case: RTuple (known-length tuple, index-based iteration) + # Special case: RTuple (known-length tuple, struct field iteration) if isinstance(rtyp, RTuple): expr_reg = builder.accept(expr) - target_type = builder.get_sequence_type(expr) - for_tuple = ForSequence(builder, index, body_block, loop_exit, line, nested) - for_tuple.init(expr_reg, target_type, reverse=False) - return for_tuple + return ForUnrolledRTuple(builder, index, body_block, loop_exit, line, rtyp, expr_reg, expr, body_insts) # Special case: string literal (unroll the loop) if isinstance(expr, StrExpr): return ForUnrolledStringLiteral(builder, index, body_block, loop_exit, line, expr.value, expr, body_insts) + # Special case: string literal (unroll the loop) + if isinstance(expr, BytesExpr): + return ForUnrolledBytesLiteral(builder, index, body_block, loop_exit, line, expr.value, expr, body_insts) + if is_sequence_rprimitive(rtyp): # Special case "for x in ". expr_reg = builder.accept(expr) @@ -790,7 +816,28 @@ def gen_step(self) -> None: pass -class ForUnrolledLiteral(ForGenerator): +class _ForUnrolled(ForGenerator): + """Generate IR for a for loop over a value known at compile time by unrolling the loop. + + This class emits the loop body for each element of the value literal directly, + avoiding any runtime iteration logic and generator handling. + """ + + def __init__(self, *args: Any, **kwargs: Any): + if type(self) is _ForUnrolled: + raise NotImplementedError("This is a base class and should not be initialized directly.") + super().__init__(*args, **kwargs) + + def gen_condition(self) -> None: + # Unrolled: nothing to do here. + pass + + def gen_step(self) -> None: + # Unrolled: nothing to do here. + pass + + +class ForUnrolledSequenceLiteral(_ForUnrolled): """Generate IR for a for loop over a tuple literal by unrolling the loop. This class emits the loop body for each element of the tuple literal directly, @@ -805,31 +852,25 @@ def __init__( body_block: BasicBlock, loop_exit: BasicBlock, line: int, - items: list[Expression], - expr: Expression, + expr: Union[ListExpr, SetExpr, TupleExpr], body_insts: GenFunc, ) -> None: super().__init__(builder, index, body_block, loop_exit, line, nested=False) - self.items = items self.expr = expr + self.items = expr.items self.body_insts = body_insts - - def gen_condition(self) -> None: - # Unrolled: nothing to do here. - pass + self.item_types = [builder.node_type(item) for item in self.items] def begin_body(self) -> None: builder = self.builder - for item in self.items: - builder.assign(builder.get_assignment_target(self.index), builder.accept(item), self.line) + for item, item_type in zip(self.items, self.item_types): + value = builder.accept(item) + value = builder.coerce(value, item_type, self.line) + builder.assign(builder.get_assignment_target(self.index), value, self.line) self.body_insts() - def gen_step(self) -> None: - # Unrolled: nothing to do here. - pass - -class ForUnrolledStringLiteral(ForGenerator): +class ForUnrolledStringLiteral(_ForUnrolled): """Generate IR for a for loop over a string literal by unrolling the loop. This class emits the loop body for each character of the string literal directly, @@ -853,10 +894,6 @@ def __init__( self.expr = expr self.body_insts = body_insts - def gen_condition(self) -> None: - # Unrolled: nothing to do here. - pass - def begin_body(self) -> None: builder = self.builder for c in self.value: @@ -867,9 +904,74 @@ def begin_body(self) -> None: ) self.body_insts() - def gen_step(self) -> None: - # Unrolled: nothing to do here. - pass + +class ForUnrolledBytesLiteral(_ForUnrolled): + """Generate IR for a for loop over a string literal by unrolling the loop. + + This class emits the loop body for each character of the string literal directly, + avoiding any runtime iteration logic. + """ + handles_body_insts = True + + def __init__( + self, + builder: IRBuilder, + index: Lvalue, + body_block: BasicBlock, + loop_exit: BasicBlock, + line: int, + value: bytes, + expr: Expression, + body_insts: GenFunc, + ) -> None: + super().__init__(builder, index, body_block, loop_exit, line, nested=False) + self.value = value + self.expr = expr + self.body_insts = body_insts + + def begin_body(self) -> None: + builder = self.builder + for c in self.value: + builder.assign( + builder.get_assignment_target(self.index), + builder.accept(IntExpr(c)), + self.line, + ) + self.body_insts() + + +class ForUnrolledRTuple(_ForUnrolled): + """Generate IR for a for loop over an RTuple by directly accessing struct fields.""" + + handles_body_insts = True + + def __init__( + self, + builder: IRBuilder, + index: Lvalue, + body_block: BasicBlock, + loop_exit: BasicBlock, + line: int, + rtuple_type: RTuple, + expr_reg: Value, + expr: Expression, + body_insts: GenFunc, + ) -> None: + super().__init__(builder, index, body_block, loop_exit, line, nested=False) + self.rtuple_type = rtuple_type + self.expr_reg = expr_reg + self.expr = expr + self.body_insts = body_insts + + def begin_body(self) -> None: + builder = self.builder + line = self.line + for i, item_type in enumerate(self.rtuple_type.types): + # Directly access the struct field for each RTuple element + value = builder.add(TupleGet(self.expr_reg, i, line)) + value = builder.coerce(value, item_type, line) + builder.assign(builder.get_assignment_target(self.index), value, line) + self.body_insts() def unsafe_index(builder: IRBuilder, target: Value, index: Value, line: int) -> Value: diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index da7b01da0ba9..c6d0d5ded2d2 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -3605,15 +3605,17 @@ def f(t: Tuple[int, int]) -> int: [out] def f(t): t :: tuple[int, int] - s, x :: int - r0 :: int + s, r0, x, r1, r2, r3 :: int +L0: s = 0 - x = t.f0 - L1: - s = CPyTagged_Add(s, x) - x = t.f1 - L2: - s = CPyTagged_Add(s, x) + r0 = t[0] + x = r0 + r1 = CPyTagged_Add(s, x) + s = r1 + r2 = t[1] + x = r2 + r3 = CPyTagged_Add(s, x) + s = r3 return s [case testForOverStringVar] @@ -3640,3 +3642,12 @@ def f(s): goto L1 L4: return out + +[case TestForOverCompledTupleExpr] +def f() -> None: + abc = (1, 2, str(3)) + for x in abc: + y = x +[out] +def f(): + abc :: tuple[int, int, str] \ No newline at end of file diff --git a/mypyc/test-data/irbuild-set.test b/mypyc/test-data/irbuild-set.test index 190ecb998198..55823dbe5a74 100644 --- a/mypyc/test-data/irbuild-set.test +++ b/mypyc/test-data/irbuild-set.test @@ -129,41 +129,41 @@ L4: def test2(): r0, tmp_tuple :: tuple[int, int, int] r1 :: set - r2 :: native_int - r3 :: object - r4 :: native_int - r5, r6 :: bit - r7 :: object - r8 :: bit - r9, r10, r12 :: int - r13 :: object - r14, x, r15 :: int - r16 :: object - r17 :: i32 - r18 :: bit - r19 :: native_int + r2, x, r3 :: int + r4 :: object + r5 :: i32 + r6 :: bit + r7, r8 :: int + r9 :: object + r10 :: i32 + r11 :: bit + r12, r13 :: int + r14 :: object + r15 :: i32 + r16 :: bit b :: set L0: r0 = (2, 6, 10) tmp_tuple = r0 r1 = PySet_New(0) - r2 = box(tuple[int, int, int], tmp_tuple) - r3 = PyObject_GetIter(r2) -L1: - r4 = PyIter_Next(r3) - if is_error(r4) goto L4 else goto L2 -L2: - r5 = unbox(int, r4) - x = r5 - r6 = f(x) - r7 = box(int, r6) - r8 = PySet_Add(r1, r7) - r9 = r8 >= 0 :: signed -L3: - goto L1 -L4: - r10 = CPy_NoErrOccurred() -L5: + r2 = tmp_tuple[0] + x = r2 + r3 = f(x) + r4 = box(int, r3) + r5 = PySet_Add(r1, r4) + r6 = r5 >= 0 :: signed + r7 = tmp_tuple[1] + x = r7 + r8 = f(x) + r9 = box(int, r8) + r10 = PySet_Add(r1, r9) + r11 = r10 >= 0 :: signed + r12 = tmp_tuple[2] + x = r12 + r13 = f(x) + r14 = box(int, r13) + r15 = PySet_Add(r1, r14) + r16 = r15 >= 0 :: signed b = r1 return 1 def test3(): @@ -735,25 +735,15 @@ def not_precomputed() -> None: [out] def precomputed(): - r0 :: set - r1, r2 :: object - r3 :: str + r0 :: str _ :: object - r4 :: bit L0: - r0 = frozenset({'False', 'None', 'True'}) - r1 = PyObject_GetIter(r0) -L1: - r2 = PyIter_Next(r1) - if is_error(r2) goto L4 else goto L2 -L2: - r3 = cast(str, r2) - _ = r3 -L3: - goto L1 -L4: - r4 = CPy_NoErrOccurred() -L5: + r0 = 'None' + _ = r0 + r1 = 'True' + _ = r1 + r2 = 'False' + _ = r2 return 1 def precomputed2(): r0 :: set From 36df4283727cf4dd9d522cd2c2f8daa738514305 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 28 Jul 2025 00:56:21 +0000 Subject: [PATCH 03/16] feat: BasicBlock repr --- mypyc/ir/ops.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index 62ac9b8d48e4..7fb9f4bfba3a 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -98,6 +98,9 @@ def __init__(self, label: int = -1) -> None: self.ops: list[Op] = [] self.error_handler: BasicBlock | None = None self.referenced = False + + def __repr__(self) -> str: + return f"{type(self).__name__}(label={self.label}, ops={self.ops})" @property def terminated(self) -> bool: From bb36ef3629d7ec0e9d63be6c8b6e08f4c9d51c19 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 28 Jul 2025 00:56:21 +0000 Subject: [PATCH 04/16] feat: BasicBlock repr --- mypyc/irbuild/for_helpers.py | 49 +++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 1a2332a5b502..0480966c4142 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -86,7 +86,6 @@ GenFunc = Callable[[], None] - def for_loop_helper( builder: IRBuilder, index: Lvalue, @@ -95,6 +94,7 @@ def for_loop_helper( else_insts: GenFunc | None, is_async: bool, line: int, + can_unroll: bool = True, ) -> None: """Generate IR for a loop. @@ -103,6 +103,7 @@ def for_loop_helper( expr: the expression to iterate over body_insts: a function that generates the body of the loop else_insts: a function that generates the else block instructions + can_unroll: whether unrolling is allowed (for semantic safety) """ # Body of the loop body_block = BasicBlock() @@ -117,15 +118,19 @@ def for_loop_helper( normal_loop_exit = else_block if else_insts is not None else exit_block for_gen = make_for_loop_generator( - builder, index, expr, body_block, normal_loop_exit, line, is_async=is_async, body_insts=body_insts + builder, index, expr, body_block, normal_loop_exit, line, is_async=is_async, body_insts=body_insts, can_unroll=can_unroll ) is_literal_loop: bool = getattr(for_gen, "handles_body_insts", False) # Only call body_insts if not handled by unrolled generator if is_literal_loop: - for_gen.begin_body() - return + try: + for_gen.begin_body() + return + except AssertionError: + # For whatever reason, we can't unpack the loop in this case. + pass builder.push_loop_stack(step_block, exit_block) condition_block = BasicBlock() @@ -420,10 +425,12 @@ def make_for_loop_generator( is_async: bool = False, nested: bool = False, body_insts: GenFunc = None, + can_unroll: bool = True, ) -> ForGenerator: """Return helper object for generating a for loop over an iterable. If "nested" is True, this is a nested iterator such as "e" in "enumerate(e)". + can_unroll: whether unrolling is allowed (for semantic safety) """ # Do an async loop if needed. async is always generic @@ -437,22 +444,24 @@ def make_for_loop_generator( rtyp = builder.node_type(expr) - # Special case: tuple literal (unroll the loop) - if is_iterable_expr_with_literal_mambers(expr): - return ForUnrolledSequenceLiteral(builder, index, body_block, loop_exit, line, expr, body_insts) - - # Special case: RTuple (known-length tuple, struct field iteration) - if isinstance(rtyp, RTuple): - expr_reg = builder.accept(expr) - return ForUnrolledRTuple(builder, index, body_block, loop_exit, line, rtyp, expr_reg, expr, body_insts) - - # Special case: string literal (unroll the loop) - if isinstance(expr, StrExpr): - return ForUnrolledStringLiteral(builder, index, body_block, loop_exit, line, expr.value, expr, body_insts) - - # Special case: string literal (unroll the loop) - if isinstance(expr, BytesExpr): - return ForUnrolledBytesLiteral(builder, index, body_block, loop_exit, line, expr.value, expr, body_insts) + + if can_unroll: + # Special case: tuple/list/set literal (unroll the loop) + if is_iterable_expr_with_literal_mambers(expr): + return ForUnrolledSequenceLiteral(builder, index, body_block, loop_exit, line, expr, body_insts) + + # Special case: RTuple (known-length tuple, struct field iteration) + if isinstance(rtyp, RTuple): + expr_reg = builder.accept(expr) + return ForUnrolledRTuple(builder, index, body_block, loop_exit, line, rtyp, expr_reg, expr, body_insts) + + # Special case: string literal (unroll the loop) + if isinstance(expr, StrExpr): + return ForUnrolledStringLiteral(builder, index, body_block, loop_exit, line, expr.value, expr, body_insts) + + # Special case: string literal (unroll the loop) + if isinstance(expr, BytesExpr): + return ForUnrolledBytesLiteral(builder, index, body_block, loop_exit, line, expr.value, expr, body_insts) if is_sequence_rprimitive(rtyp): # Special case "for x in ". From fce759db855f8e68acb8405bda8135c3a89c2b6b Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 28 Jul 2025 00:56:21 +0000 Subject: [PATCH 05/16] more stuff --- mypyc/irbuild/for_helpers.py | 3 +- mypyc/irbuild/statement.py | 51 +++++++++++++++++++++-- mypyc/test-data/irbuild-basic.test | 65 ++++++++++++++++++++++-------- mypyc/test-data/irbuild-set.test | 1 + 4 files changed, 100 insertions(+), 20 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 0480966c4142..382bba9bf819 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -86,6 +86,7 @@ GenFunc = Callable[[], None] + def for_loop_helper( builder: IRBuilder, index: Lvalue, @@ -94,7 +95,7 @@ def for_loop_helper( else_insts: GenFunc | None, is_async: bool, line: int, - can_unroll: bool = True, + can_unroll: bool = False, ) -> None: """Generate IR for a loop. diff --git a/mypyc/irbuild/statement.py b/mypyc/irbuild/statement.py index eeeb40ac672f..0ecac88a6402 100644 --- a/mypyc/irbuild/statement.py +++ b/mypyc/irbuild/statement.py @@ -13,6 +13,7 @@ from typing import Callable import mypy.nodes +import mypy.traverser from mypy.nodes import ( ARG_NAMED, ARG_POS, @@ -130,6 +131,29 @@ ValueGenFunc = Callable[[], Value] +class ControlFlowDetector(mypy.traverser.TraverserVisitor): + """ + A Visitor class that detects whether a block contains any of the following control flow statements: + + - return + - continue + - break + + """ + def __init__(self) -> None: + super().__init__() + self.has_control_flow = False + + def visit_break_stmt(self, o: BreakStmt) -> None: + self.has_control_flow = True + + def visit_continue_stmt(self, o: ContinueStmt) -> None: + self.has_control_flow = True + + def visit_return_stmt(self, o: ReturnStmt) -> None: + self.has_control_flow = True + + def transform_block(builder: IRBuilder, block: Block) -> None: if not block.is_unreachable: builder.block_reachable_stack.append(True) @@ -434,6 +458,11 @@ def transform_while_stmt(builder: IRBuilder, s: WhileStmt) -> None: def transform_for_stmt(builder: IRBuilder, s: ForStmt) -> None: + # Check for control flow statements in the loop body + detector = ControlFlowDetector() + s.body.accept(detector) + can_unroll = s.else_body is None and not detector.has_control_flow + def body() -> None: builder.accept(s.body) @@ -441,9 +470,25 @@ def else_block() -> None: assert s.else_body is not None builder.accept(s.else_body) - for_loop_helper( - builder, s.index, s.expr, body, else_block if s.else_body else None, s.is_async, s.line - ) + while True: + try: + # for whatever reason this can blow up with can_unroll=True + # but in those cases we can just fall back to the existing for loop logic. + for_loop_helper( + builder, + s.index, + s.expr, + body, + else_block if s.else_body else None, + s.is_async, + s.line, + can_unroll=can_unroll, + ) + return + except AssertionError: + if not can_unroll: + raise + can_unroll = False def transform_break_stmt(builder: IRBuilder, node: BreakStmt) -> None: diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index c6d0d5ded2d2..5fe757e09cea 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -3626,22 +3626,32 @@ def f(s: str) -> str: return out [out] def f(s): - s :: str + s, r0, out :: str + r1, r2 :: native_int + r3, r4 :: bit + r5, c, r6 :: str + r7 :: native_int out, c :: str - r0 :: int - L0: - out = '' - r0 = 0 - L1: - r1 = r0 < CPyStr_Size(s) - if r1 goto L2 else goto L4 :: bool - L2: - c = CPyStr_GetItemUnsafe(s, r0) - out = CPyStr_Append(out, c) - r0 = r0 + 1 - goto L1 - L4: - return out +L0: + r0 = '' + out = r0 + r1 = 0 +L1: + r2 = CPyStr_Size_size_t(s) + r3 = r2 >= 0 :: signed + r4 = r1 < r2 :: signed + if r4 goto L2 else goto L2 :: bool +L2: + r5 = CPyStr_GetItemUnsafe(s, r1) + c = r5 + r6 = CPyStr_Append(out, c) + out = r6 +L3: + r7 = r1 + 1 + r1 = r7 + goto L1 +L4: + return out [case TestForOverCompledTupleExpr] def f() -> None: @@ -3650,4 +3660,27 @@ def f() -> None: y = x [out] def f(): - abc :: tuple[int, int, str] \ No newline at end of file + r0 :: str + r1, abc :: tuple[int, int, str] + r2 :: int + r3 :: object + x, y :: union[int, str] + r4 :: int + r5 :: object + r6 :: str +L0: + r0 = CPyTagged_Str(6) + r1 = (2, 4, r0) + abc = r1 + r2 = abc[0] + r3 = box(int, r2) + x = r3 + y = x + r4 = abc[1] + r5 = box(int, r4) + x = r5 + y = x + r6 = abc[2] + x = r6 + y = x + return 1 \ No newline at end of file diff --git a/mypyc/test-data/irbuild-set.test b/mypyc/test-data/irbuild-set.test index 55823dbe5a74..aea36385fb13 100644 --- a/mypyc/test-data/irbuild-set.test +++ b/mypyc/test-data/irbuild-set.test @@ -737,6 +737,7 @@ def not_precomputed() -> None: def precomputed(): r0 :: str _ :: object + r1, r2 :: str L0: r0 = 'None' _ = r0 From ae526b4d0f125d76198de26f57bcebb4bac81330 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 00:59:07 +0000 Subject: [PATCH 06/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/ir/ops.py | 2 +- mypyc/irbuild/for_helpers.py | 53 ++++++++++++++++++++---------- mypyc/irbuild/statement.py | 3 +- mypyc/test-data/irbuild-basic.test | 2 +- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index 7fb9f4bfba3a..e6a10317a495 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -98,7 +98,7 @@ def __init__(self, label: int = -1) -> None: self.ops: list[Op] = [] self.error_handler: BasicBlock | None = None self.referenced = False - + def __repr__(self) -> str: return f"{type(self).__name__}(label={self.label}, ops={self.ops})" diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 382bba9bf819..bb2c5f70da22 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -119,11 +119,19 @@ def for_loop_helper( normal_loop_exit = else_block if else_insts is not None else exit_block for_gen = make_for_loop_generator( - builder, index, expr, body_block, normal_loop_exit, line, is_async=is_async, body_insts=body_insts, can_unroll=can_unroll + builder, + index, + expr, + body_block, + normal_loop_exit, + line, + is_async=is_async, + body_insts=body_insts, + can_unroll=can_unroll, ) is_literal_loop: bool = getattr(for_gen, "handles_body_insts", False) - + # Only call body_insts if not handled by unrolled generator if is_literal_loop: try: @@ -399,7 +407,11 @@ def is_literal_expr(expr: Expression) -> bool: # Add other literal types as needed if isinstance(expr, (IntExpr, StrExpr, FloatExpr, BytesExpr)): return True - if isinstance(expr, NameExpr) and expr.fullname in {"builtins.None", "builtins.True", "builtins.False"}: + if isinstance(expr, NameExpr) and expr.fullname in { + "builtins.None", + "builtins.True", + "builtins.False", + }: return True return False @@ -409,8 +421,7 @@ def is_iterable_expr_with_literal_mambers(expr: Expression) -> bool: isinstance(expr, (ListExpr, SetExpr, TupleExpr)) and not isinstance(expr, MemberExpr) and all( - is_literal_expr(item) - or is_iterable_expr_with_literal_mambers(item) + is_literal_expr(item) or is_iterable_expr_with_literal_mambers(item) for item in expr.items ) ) @@ -445,24 +456,31 @@ def make_for_loop_generator( rtyp = builder.node_type(expr) - if can_unroll: # Special case: tuple/list/set literal (unroll the loop) if is_iterable_expr_with_literal_mambers(expr): - return ForUnrolledSequenceLiteral(builder, index, body_block, loop_exit, line, expr, body_insts) + return ForUnrolledSequenceLiteral( + builder, index, body_block, loop_exit, line, expr, body_insts + ) # Special case: RTuple (known-length tuple, struct field iteration) if isinstance(rtyp, RTuple): expr_reg = builder.accept(expr) - return ForUnrolledRTuple(builder, index, body_block, loop_exit, line, rtyp, expr_reg, expr, body_insts) + return ForUnrolledRTuple( + builder, index, body_block, loop_exit, line, rtyp, expr_reg, expr, body_insts + ) # Special case: string literal (unroll the loop) if isinstance(expr, StrExpr): - return ForUnrolledStringLiteral(builder, index, body_block, loop_exit, line, expr.value, expr, body_insts) + return ForUnrolledStringLiteral( + builder, index, body_block, loop_exit, line, expr.value, expr, body_insts + ) # Special case: string literal (unroll the loop) if isinstance(expr, BytesExpr): - return ForUnrolledBytesLiteral(builder, index, body_block, loop_exit, line, expr.value, expr, body_insts) + return ForUnrolledBytesLiteral( + builder, index, body_block, loop_exit, line, expr.value, expr, body_insts + ) if is_sequence_rprimitive(rtyp): # Special case "for x in ". @@ -835,7 +853,9 @@ class _ForUnrolled(ForGenerator): def __init__(self, *args: Any, **kwargs: Any): if type(self) is _ForUnrolled: - raise NotImplementedError("This is a base class and should not be initialized directly.") + raise NotImplementedError( + "This is a base class and should not be initialized directly." + ) super().__init__(*args, **kwargs) def gen_condition(self) -> None: @@ -853,6 +873,7 @@ class ForUnrolledSequenceLiteral(_ForUnrolled): This class emits the loop body for each element of the tuple literal directly, avoiding any runtime iteration logic. """ + handles_body_insts = True def __init__( @@ -886,6 +907,7 @@ class ForUnrolledStringLiteral(_ForUnrolled): This class emits the loop body for each character of the string literal directly, avoiding any runtime iteration logic. """ + handles_body_insts = True def __init__( @@ -908,9 +930,7 @@ def begin_body(self) -> None: builder = self.builder for c in self.value: builder.assign( - builder.get_assignment_target(self.index), - builder.accept(StrExpr(c)), - self.line, + builder.get_assignment_target(self.index), builder.accept(StrExpr(c)), self.line ) self.body_insts() @@ -921,6 +941,7 @@ class ForUnrolledBytesLiteral(_ForUnrolled): This class emits the loop body for each character of the string literal directly, avoiding any runtime iteration logic. """ + handles_body_insts = True def __init__( @@ -943,9 +964,7 @@ def begin_body(self) -> None: builder = self.builder for c in self.value: builder.assign( - builder.get_assignment_target(self.index), - builder.accept(IntExpr(c)), - self.line, + builder.get_assignment_target(self.index), builder.accept(IntExpr(c)), self.line ) self.body_insts() diff --git a/mypyc/irbuild/statement.py b/mypyc/irbuild/statement.py index 0ecac88a6402..e2399ef21504 100644 --- a/mypyc/irbuild/statement.py +++ b/mypyc/irbuild/statement.py @@ -138,8 +138,9 @@ class ControlFlowDetector(mypy.traverser.TraverserVisitor): - return - continue - break - + """ + def __init__(self) -> None: super().__init__() self.has_control_flow = False diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 5fe757e09cea..83f79bd15065 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -3683,4 +3683,4 @@ L0: r6 = abc[2] x = r6 y = x - return 1 \ No newline at end of file + return 1 From 802a6dc24110fbb3dfb33b421129ab8113e88a12 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Sun, 3 Aug 2025 20:59:16 +0000 Subject: [PATCH 07/16] fix; zip --- mypyc/irbuild/for_helpers.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index bb2c5f70da22..b940570e364c 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -891,6 +891,21 @@ def __init__( self.items = expr.items self.body_insts = body_insts self.item_types = [builder.node_type(item) for item in self.items] + self.index_target = builder.maybe_spill_assignable(Integer(0, c_pyssize_t_rprimitive)) + + def gen_condition(self) -> None: + # For unrolled loops, immediately jump to the body if there are items, + # otherwise jump to the loop exit. + # NOTE: this method is not used when the loop is fully unrolled, but is when the loop is a component of another loop, ie a ForZip + builder = self.builder + comparison = builder.binary_op(builder.read(self.index_target, self.line), Integer(len(self.items)), "<", self.line) + builder.add_bool_branch(comparison, self.body_block, self.loop_exit) + + def gen_step(self) -> None: + # NOTE: this method is not used when the loop is fully unrolled, but is when the loop is a component of another loop, ie a ForZip + builder = self.builder + add = builder.builder.int_add(builder.read(self.index_target, self.line), 1) + builder.assign(self.index_target, add, self.line) def begin_body(self) -> None: builder = self.builder From 8124aa2989c2012f833d2a0760306d8884a9187d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 3 Aug 2025 21:01:41 +0000 Subject: [PATCH 08/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/for_helpers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index b940570e364c..c2c1002f975d 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -898,9 +898,11 @@ def gen_condition(self) -> None: # otherwise jump to the loop exit. # NOTE: this method is not used when the loop is fully unrolled, but is when the loop is a component of another loop, ie a ForZip builder = self.builder - comparison = builder.binary_op(builder.read(self.index_target, self.line), Integer(len(self.items)), "<", self.line) + comparison = builder.binary_op( + builder.read(self.index_target, self.line), Integer(len(self.items)), "<", self.line + ) builder.add_bool_branch(comparison, self.body_block, self.loop_exit) - + def gen_step(self) -> None: # NOTE: this method is not used when the loop is fully unrolled, but is when the loop is a component of another loop, ie a ForZip builder = self.builder From 26f390f72880dad880ddda37c6f9297ef896fc8b Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 3 Aug 2025 17:09:06 -0400 Subject: [PATCH 09/16] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index c2c1002f975d..576c0db56241 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -436,7 +436,7 @@ def make_for_loop_generator( line: int, is_async: bool = False, nested: bool = False, - body_insts: GenFunc = None, + body_insts: Optional[GenFunc] = None, can_unroll: bool = True, ) -> ForGenerator: """Return helper object for generating a for loop over an iterable. @@ -456,7 +456,7 @@ def make_for_loop_generator( rtyp = builder.node_type(expr) - if can_unroll: + if can_unroll and body_insts is not None: # Special case: tuple/list/set literal (unroll the loop) if is_iterable_expr_with_literal_mambers(expr): return ForUnrolledSequenceLiteral( From 4c35bd41066ed895c1214899e245922c6e056806 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 3 Aug 2025 17:10:54 -0400 Subject: [PATCH 10/16] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 576c0db56241..ce853af11b2a 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -460,7 +460,7 @@ def make_for_loop_generator( # Special case: tuple/list/set literal (unroll the loop) if is_iterable_expr_with_literal_mambers(expr): return ForUnrolledSequenceLiteral( - builder, index, body_block, loop_exit, line, expr, body_insts + builder, index, body_block, loop_exit, line, expr, body_insts # type: ignore [arg-type] ) # Special case: RTuple (known-length tuple, struct field iteration) @@ -476,10 +476,10 @@ def make_for_loop_generator( builder, index, body_block, loop_exit, line, expr.value, expr, body_insts ) - # Special case: string literal (unroll the loop) + # Special case: bytes literal (unroll the loop) if isinstance(expr, BytesExpr): return ForUnrolledBytesLiteral( - builder, index, body_block, loop_exit, line, expr.value, expr, body_insts + builder, index, body_block, loop_exit, line, expr.value.encode(), expr, body_insts ) if is_sequence_rprimitive(rtyp): From 66419eabb07a519246c0396dfea9dbcf9879a30d Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 3 Aug 2025 17:15:32 -0400 Subject: [PATCH 11/16] Update for_helpers.py --- 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 ce853af11b2a..f781058f2de2 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -436,7 +436,7 @@ def make_for_loop_generator( line: int, is_async: bool = False, nested: bool = False, - body_insts: Optional[GenFunc] = None, + body_insts: GenFunc | None = None, can_unroll: bool = True, ) -> ForGenerator: """Return helper object for generating a for loop over an iterable. From 777ad247c87f4be8286fd6b0f61ea6a2bfda72ef Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 3 Aug 2025 17:20:09 -0400 Subject: [PATCH 12/16] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index f781058f2de2..a2161ee22692 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -890,7 +890,6 @@ def __init__( self.expr = expr self.items = expr.items self.body_insts = body_insts - self.item_types = [builder.node_type(item) for item in self.items] self.index_target = builder.maybe_spill_assignable(Integer(0, c_pyssize_t_rprimitive)) def gen_condition(self) -> None: @@ -911,9 +910,9 @@ def gen_step(self) -> None: def begin_body(self) -> None: builder = self.builder - for item, item_type in zip(self.items, self.item_types): - value = builder.accept(item) - value = builder.coerce(value, item_type, self.line) + for expr in self.items: + value = builder.accept(expr) + #value = builder.coerce(value, builder.node_type(expr), self.line) builder.assign(builder.get_assignment_target(self.index), value, self.line) self.body_insts() From 5f85bcd0f4d20f74fed11d8a1ee6b3aa8b7008e3 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 3 Aug 2025 17:21:02 -0400 Subject: [PATCH 13/16] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index a2161ee22692..8f8ecf0c038f 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -7,7 +7,7 @@ from __future__ import annotations -from typing import Any, Callable, ClassVar, Union +from typing import Any, Callable, ClassVar from mypy.nodes import ( ARG_POS, @@ -883,7 +883,7 @@ def __init__( body_block: BasicBlock, loop_exit: BasicBlock, line: int, - expr: Union[ListExpr, SetExpr, TupleExpr], + expr: ListExpr | SetExpr | TupleExpr, body_insts: GenFunc, ) -> None: super().__init__(builder, index, body_block, loop_exit, line, nested=False) From 47c6236964bc7cdd9131af51408a50eb080e00e5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 3 Aug 2025 21:21:28 +0000 Subject: [PATCH 14/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- 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 8f8ecf0c038f..0f7ee7efcbcc 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -912,7 +912,7 @@ def begin_body(self) -> None: builder = self.builder for expr in self.items: value = builder.accept(expr) - #value = builder.coerce(value, builder.node_type(expr), self.line) + # value = builder.coerce(value, builder.node_type(expr), self.line) builder.assign(builder.get_assignment_target(self.index), value, self.line) self.body_insts() From 906c67e166e5175c16cac48c0ecdcc56a438f5f9 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 3 Aug 2025 17:30:16 -0400 Subject: [PATCH 15/16] Update for_helpers.py --- 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 0f7ee7efcbcc..943991bb5af2 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1014,7 +1014,7 @@ def begin_body(self) -> None: for i, item_type in enumerate(self.rtuple_type.types): # Directly access the struct field for each RTuple element value = builder.add(TupleGet(self.expr_reg, i, line)) - value = builder.coerce(value, item_type, line) + #value = builder.coerce(value, item_type, line) builder.assign(builder.get_assignment_target(self.index), value, line) self.body_insts() From e68c44030fd249f86c9fddfdc85fb9d4494cf7f5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 3 Aug 2025 21:31:47 +0000 Subject: [PATCH 16/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- 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 943991bb5af2..e9b0fce05821 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1014,7 +1014,7 @@ def begin_body(self) -> None: for i, item_type in enumerate(self.rtuple_type.types): # Directly access the struct field for each RTuple element value = builder.add(TupleGet(self.expr_reg, i, line)) - #value = builder.coerce(value, item_type, line) + # value = builder.coerce(value, item_type, line) builder.assign(builder.get_assignment_target(self.index), value, line) self.body_insts()