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+
767875def 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
0 commit comments