Skip to content

Commit 126f300

Browse files
committed
working tuple stuff
1 parent a8d2f13 commit 126f300

File tree

6 files changed

+220
-10
lines changed

6 files changed

+220
-10
lines changed

mypyc/irbuild/for_helpers.py

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
NameExpr,
2121
RefExpr,
2222
SetExpr,
23+
StrExpr,
2324
TupleExpr,
2425
TypeAlias,
2526
)
@@ -112,9 +113,16 @@ def for_loop_helper(
112113
normal_loop_exit = else_block if else_insts is not None else exit_block
113114

114115
for_gen = make_for_loop_generator(
115-
builder, index, expr, body_block, normal_loop_exit, line, is_async=is_async
116+
builder, index, expr, body_block, normal_loop_exit, line, is_async=is_async, body_insts=body_insts
116117
)
117118

119+
is_literal_loop: bool = getattr(for_gen, "handles_body_insts", False)
120+
121+
# Only call body_insts if not handled by unrolled generator
122+
if is_literal_loop:
123+
for_gen.begin_body()
124+
return
125+
118126
builder.push_loop_stack(step_block, exit_block)
119127
condition_block = BasicBlock()
120128
builder.goto_and_activate(condition_block)
@@ -386,6 +394,7 @@ def make_for_loop_generator(
386394
line: int,
387395
is_async: bool = False,
388396
nested: bool = False,
397+
body_insts: GenFunc = None,
389398
) -> ForGenerator:
390399
"""Return helper object for generating a for loop over an iterable.
391400
@@ -402,6 +411,23 @@ def make_for_loop_generator(
402411
return async_obj
403412

404413
rtyp = builder.node_type(expr)
414+
415+
# Special case: tuple literal (unroll the loop)
416+
if isinstance(expr, TupleExpr):
417+
return ForUnrolledLiteral(builder, index, body_block, loop_exit, line, expr.items, expr, body_insts)
418+
419+
# Special case: RTuple (known-length tuple, index-based iteration)
420+
if isinstance(rtyp, RTuple):
421+
expr_reg = builder.accept(expr)
422+
target_type = builder.get_sequence_type(expr)
423+
for_tuple = ForSequence(builder, index, body_block, loop_exit, line, nested)
424+
for_tuple.init(expr_reg, target_type, reverse=False)
425+
return for_tuple
426+
427+
# Special case: string literal (unroll the loop)
428+
if isinstance(expr, StrExpr):
429+
return ForUnrolledStringLiteral(builder, index, body_block, loop_exit, line, expr.value, expr, body_insts)
430+
405431
if is_sequence_rprimitive(rtyp):
406432
# Special case "for x in <list>".
407433
expr_reg = builder.accept(expr)
@@ -764,6 +790,88 @@ def gen_step(self) -> None:
764790
pass
765791

766792

793+
class ForUnrolledLiteral(ForGenerator):
794+
"""Generate IR for a for loop over a tuple literal by unrolling the loop.
795+
796+
This class emits the loop body for each element of the tuple literal directly,
797+
avoiding any runtime iteration logic.
798+
"""
799+
handles_body_insts = True
800+
801+
def __init__(
802+
self,
803+
builder: IRBuilder,
804+
index: Lvalue,
805+
body_block: BasicBlock,
806+
loop_exit: BasicBlock,
807+
line: int,
808+
items: list[Expression],
809+
expr: Expression,
810+
body_insts: GenFunc,
811+
) -> None:
812+
super().__init__(builder, index, body_block, loop_exit, line, nested=False)
813+
self.items = items
814+
self.expr = expr
815+
self.body_insts = body_insts
816+
817+
def gen_condition(self) -> None:
818+
# Unrolled: nothing to do here.
819+
pass
820+
821+
def begin_body(self) -> None:
822+
builder = self.builder
823+
for item in self.items:
824+
builder.assign(builder.get_assignment_target(self.index), builder.accept(item), self.line)
825+
self.body_insts()
826+
827+
def gen_step(self) -> None:
828+
# Unrolled: nothing to do here.
829+
pass
830+
831+
832+
class ForUnrolledStringLiteral(ForGenerator):
833+
"""Generate IR for a for loop over a string literal by unrolling the loop.
834+
835+
This class emits the loop body for each character of the string literal directly,
836+
avoiding any runtime iteration logic.
837+
"""
838+
handles_body_insts = True
839+
840+
def __init__(
841+
self,
842+
builder: IRBuilder,
843+
index: Lvalue,
844+
body_block: BasicBlock,
845+
loop_exit: BasicBlock,
846+
line: int,
847+
value: str,
848+
expr: Expression,
849+
body_insts: GenFunc,
850+
) -> None:
851+
super().__init__(builder, index, body_block, loop_exit, line, nested=False)
852+
self.value = value
853+
self.expr = expr
854+
self.body_insts = body_insts
855+
856+
def gen_condition(self) -> None:
857+
# Unrolled: nothing to do here.
858+
pass
859+
860+
def begin_body(self) -> None:
861+
builder = self.builder
862+
for c in self.value:
863+
builder.assign(
864+
builder.get_assignment_target(self.index),
865+
builder.accept(StrExpr(c)),
866+
self.line,
867+
)
868+
self.body_insts()
869+
870+
def gen_step(self) -> None:
871+
# Unrolled: nothing to do here.
872+
pass
873+
874+
767875
def unsafe_index(builder: IRBuilder, target: Value, index: Value, line: int) -> Value:
768876
"""Emit a potentially unsafe index into a target."""
769877
# This doesn't really fit nicely into any of our data-driven frameworks

mypyc/irbuild/ll_builder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ def goto(self, target: BasicBlock) -> None:
273273
def activate_block(self, block: BasicBlock) -> None:
274274
"""Add a basic block and make it the active one (target of adds)."""
275275
if self.blocks:
276-
assert self.blocks[-1].terminated
276+
assert self.blocks[-1].terminated, self.blocks[-1]
277277

278278
block.error_handler = self.error_handlers[-1]
279279
self.blocks.append(block)

mypyc/test-data/irbuild-basic.test

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3546,3 +3546,97 @@ L0:
35463546
r2 = PyObject_Vectorcall(r1, 0, 0, 0)
35473547
r3 = box(None, 1)
35483548
return r3
3549+
3550+
[case testForOverTupleLiteral]
3551+
def f() -> int:
3552+
s = 0
3553+
for x in (1, 2, 3):
3554+
s += x
3555+
return s
3556+
[out]
3557+
def f():
3558+
s, x, r0, r1, r2 :: int
3559+
L0:
3560+
s = 0
3561+
x = 2
3562+
r0 = CPyTagged_Add(s, x)
3563+
s = r0
3564+
x = 4
3565+
r1 = CPyTagged_Add(s, x)
3566+
s = r1
3567+
x = 6
3568+
r2 = CPyTagged_Add(s, x)
3569+
s = r2
3570+
return s
3571+
3572+
[case testForOverStringLiteral]
3573+
def f() -> str:
3574+
out = ""
3575+
for c in "abc":
3576+
out += c
3577+
return out
3578+
[out]
3579+
def f():
3580+
r0, out, r1, c, r2, r3, r4, r5, r6 :: str
3581+
L0:
3582+
r0 = ''
3583+
out = r0
3584+
r1 = 'a'
3585+
c = r1
3586+
r2 = CPyStr_Append(out, c)
3587+
out = r2
3588+
r3 = 'b'
3589+
c = r3
3590+
r4 = CPyStr_Append(out, c)
3591+
out = r4
3592+
r5 = 'c'
3593+
c = r5
3594+
r6 = CPyStr_Append(out, c)
3595+
out = r6
3596+
return out
3597+
3598+
[case testForOverRTuple]
3599+
from typing import Tuple
3600+
def f(t: Tuple[int, int]) -> int:
3601+
s = 0
3602+
for x in t:
3603+
s += x
3604+
return s
3605+
[out]
3606+
def f(t):
3607+
t :: tuple[int, int]
3608+
s, x :: int
3609+
r0 :: int
3610+
s = 0
3611+
x = t.f0
3612+
L1:
3613+
s = CPyTagged_Add(s, x)
3614+
x = t.f1
3615+
L2:
3616+
s = CPyTagged_Add(s, x)
3617+
return s
3618+
3619+
[case testForOverStringVar]
3620+
def f(s: str) -> str:
3621+
out = ""
3622+
for c in s:
3623+
out += c
3624+
return out
3625+
[out]
3626+
def f(s):
3627+
s :: str
3628+
out, c :: str
3629+
r0 :: int
3630+
L0:
3631+
out = ''
3632+
r0 = 0
3633+
L1:
3634+
r1 = r0 < CPyStr_Size(s)
3635+
if r1 goto L2 else goto L4 :: bool
3636+
L2:
3637+
c = CPyStr_GetItemUnsafe(s, r0)
3638+
out = CPyStr_Append(out, c)
3639+
r0 = r0 + 1
3640+
goto L1
3641+
L4:
3642+
return out

mypyc/test-data/irbuild-set.test

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,19 @@ L4:
129129
def test2():
130130
r0, tmp_tuple :: tuple[int, int, int]
131131
r1 :: set
132-
r2, r3, r4 :: object
133-
r5, x, r6 :: int
132+
r2 :: native_int
133+
r3 :: object
134+
r4 :: native_int
135+
r5, r6 :: bit
134136
r7 :: object
135-
r8 :: i32
136-
r9, r10 :: bit
137+
r8 :: bit
138+
r9, r10, r12 :: int
139+
r13 :: object
140+
r14, x, r15 :: int
141+
r16 :: object
142+
r17 :: i32
143+
r18 :: bit
144+
r19 :: native_int
137145
b :: set
138146
L0:
139147
r0 = (2, 6, 10)

test-data/unit/pythoneval.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,9 +1131,9 @@ async def main() -> None:
11311131
reveal_type(a_y)
11321132
reveal_type(asyncio.gather(*[asyncio.sleep(1), asyncio.sleep(1)]))
11331133
[out]
1134-
_testAsyncioGatherPreciseType.py:9: note: Revealed type is "builtins.str"
11351134
_testAsyncioGatherPreciseType.py:10: note: Revealed type is "builtins.str"
1136-
_testAsyncioGatherPreciseType.py:11: note: Revealed type is "asyncio.futures.Future[builtins.list[Any]]"
1135+
_testAsyncioGatherPreciseType.py:11: note: Revealed type is "builtins.str"
1136+
_testAsyncioGatherPreciseType.py:12: note: Revealed type is "asyncio.futures.Future[builtins.list[None]]"
11371137

11381138
[case testMultipleInheritanceWorksWithTupleTypeGeneric]
11391139
from typing import SupportsAbs, NamedTuple

test-data/unit/stubgen.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -988,14 +988,14 @@ class RegularClass:
988988
from typing import NamedTuple
989989

990990
# TODO: make sure that nested classes in `NamedTuple` are supported:
991-
class NamedTupleWithNestedClass(NamedTuple):
991+
class NamedTupleWithNestedClass(NamedTuple): ...
992992
class Nested:
993993
x: int
994994
y: str = 'a'
995995
[out]
996996
from typing import NamedTuple
997997

998-
class NamedTupleWithNestedClass(NamedTuple):
998+
class NamedTupleWithNestedClass(NamedTuple): ...
999999
class Nested:
10001000
x: int
10011001
y: str

0 commit comments

Comments
 (0)