77
88from __future__ import annotations
99
10- from collections .abc import Callable , Iterator
10+ from collections .abc import Callable , Iterator , Set as AbstractSet
1111from contextlib import contextmanager
1212from typing import ClassVar , cast
1313
1414from mypy .nodes import (
1515 ARG_POS ,
16+ AssignmentExpr ,
17+ AssignmentStmt ,
1618 CallExpr ,
19+ DelStmt ,
1720 DictionaryComprehension ,
1821 Expression ,
22+ ForStmt ,
1923 GeneratorExpr ,
2024 ListExpr ,
2125 Lvalue ,
2226 MemberExpr ,
2327 NameExpr ,
28+ OperatorAssignmentStmt ,
2429 RefExpr ,
2530 SetExpr ,
2631 StarExpr ,
32+ SymbolNode ,
33+ TryStmt ,
2734 TupleExpr ,
2835 TypeAlias ,
2936 Var ,
37+ WithStmt ,
3038)
39+ from mypy .patterns import AsPattern , MappingPattern , StarredPattern
40+ from mypy .traverser import TraverserVisitor
3141from mypy .types import LiteralType , TupleType , get_proper_type , get_proper_types
3242from mypyc .common import GENERATOR_HELPER_NAME
3343from mypyc .ir .ops import (
7383from mypyc .irbuild .constant_fold import constant_fold_expr
7484from mypyc .irbuild .context import FuncInfo
7585from mypyc .irbuild .env_class import add_vars_to_env , finalize_env_class , setup_env_class
76- from mypyc .irbuild .targets import AssignmentTargetTuple
86+ from mypyc .irbuild .targets import AssignmentTargetRegister , AssignmentTargetTuple
7787from mypyc .irbuild .vec import vec_append , vec_create , vec_get_item_unsafe , vec_init_item_unsafe
7888from mypyc .primitives .dict_ops import (
7989 dict_check_size_op ,
@@ -110,6 +120,7 @@ def for_loop_helper(
110120 is_async : bool ,
111121 line : int ,
112122 after_init : GenFunc | None = None ,
123+ reassigned : AbstractSet [SymbolNode ] = frozenset (),
113124) -> None :
114125 """Generate IR for a loop.
115126
@@ -120,6 +131,8 @@ def for_loop_helper(
120131 else_insts: a function that generates the else block instructions
121132 after_init: a function that generates instructions to run after the iterable has
122133 been evaluated (and iter() called on it) but before the first iteration
134+ reassigned: local variables that the loop index or body may assign to
135+ (see find_loop_assignments)
123136 """
124137 # Body of the loop
125138 body_block = BasicBlock ()
@@ -134,7 +147,14 @@ def for_loop_helper(
134147 normal_loop_exit = else_block if else_insts is not None else exit_block
135148
136149 for_gen = make_for_loop_generator (
137- builder , index , expr , body_block , normal_loop_exit , line , is_async = is_async
150+ builder ,
151+ index ,
152+ expr ,
153+ body_block ,
154+ normal_loop_exit ,
155+ line ,
156+ is_async = is_async ,
157+ reassigned = reassigned ,
138158 )
139159 if after_init is not None :
140160 after_init ()
@@ -584,6 +604,95 @@ def is_range_ref(expr: RefExpr) -> bool:
584604 )
585605
586606
607+ def find_loop_assignments (s : ForStmt ) -> set [SymbolNode ]:
608+ """Return the variables that the index or the body of a for loop may assign to."""
609+ collector = AssignmentCollector ()
610+ collector .add_lvalue (s .index )
611+ s .body .accept (collector )
612+ return collector .symbols
613+
614+
615+ class AssignmentCollector (TraverserVisitor ):
616+ """Collect the symbols of the variables assigned to (or deleted) in a subtree."""
617+
618+ def __init__ (self ) -> None :
619+ super ().__init__ ()
620+ self .symbols : set [SymbolNode ] = set ()
621+
622+ def add_lvalue (self , lvalue : Expression | None ) -> None :
623+ if isinstance (lvalue , NameExpr ):
624+ if lvalue .node is not None :
625+ self .symbols .add (lvalue .node )
626+ elif isinstance (lvalue , (TupleExpr , ListExpr )):
627+ for item in lvalue .items :
628+ self .add_lvalue (item )
629+ elif isinstance (lvalue , StarExpr ):
630+ self .add_lvalue (lvalue .expr )
631+
632+ def visit_assignment_stmt (self , o : AssignmentStmt ) -> None :
633+ for lvalue in o .lvalues :
634+ self .add_lvalue (lvalue )
635+ super ().visit_assignment_stmt (o )
636+
637+ def visit_operator_assignment_stmt (self , o : OperatorAssignmentStmt ) -> None :
638+ self .add_lvalue (o .lvalue )
639+ super ().visit_operator_assignment_stmt (o )
640+
641+ def visit_assignment_expr (self , o : AssignmentExpr ) -> None :
642+ self .add_lvalue (o .target )
643+ super ().visit_assignment_expr (o )
644+
645+ def visit_for_stmt (self , o : ForStmt ) -> None :
646+ self .add_lvalue (o .index )
647+ super ().visit_for_stmt (o )
648+
649+ def visit_with_stmt (self , o : WithStmt ) -> None :
650+ for target in o .target :
651+ self .add_lvalue (target )
652+ super ().visit_with_stmt (o )
653+
654+ def visit_try_stmt (self , o : TryStmt ) -> None :
655+ for var in o .vars :
656+ self .add_lvalue (var )
657+ super ().visit_try_stmt (o )
658+
659+ def visit_del_stmt (self , o : DelStmt ) -> None :
660+ self .add_lvalue (o .expr )
661+ super ().visit_del_stmt (o )
662+
663+ def visit_as_pattern (self , o : AsPattern ) -> None :
664+ self .add_lvalue (o .name )
665+ super ().visit_as_pattern (o )
666+
667+ def visit_starred_pattern (self , o : StarredPattern ) -> None :
668+ self .add_lvalue (o .capture )
669+ super ().visit_starred_pattern (o )
670+
671+ def visit_mapping_pattern (self , o : MappingPattern ) -> None :
672+ self .add_lvalue (o .rest )
673+ super ().visit_mapping_pattern (o )
674+
675+
676+ def accept_loop_state (
677+ builder : IRBuilder , expr : Expression , reassigned : AbstractSet [SymbolNode ]
678+ ) -> Value :
679+ """Evaluate the object to iterate over, or the end of a range.
680+
681+ Specialized loops read this value again on every iteration. If it's the register
682+ of a local variable that the loop may assign to, copy it to a new register, since
683+ the assignment must not affect the iteration (as in "for x in a: a = []").
684+ """
685+ value = builder .accept (expr )
686+ if isinstance (value , Register ):
687+ for symbol in reassigned :
688+ target = builder .symtables [- 1 ].get (symbol )
689+ if isinstance (target , AssignmentTargetRegister ) and target .register is value :
690+ copy = Register (value .type )
691+ builder .assign (copy , value , expr .line )
692+ return copy
693+ return value
694+
695+
587696def make_for_loop_generator (
588697 builder : IRBuilder ,
589698 index : Lvalue ,
@@ -593,10 +702,14 @@ def make_for_loop_generator(
593702 line : int ,
594703 is_async : bool = False ,
595704 nested : bool = False ,
705+ reassigned : AbstractSet [SymbolNode ] = frozenset (),
596706) -> ForGenerator :
597707 """Return helper object for generating a for loop over an iterable.
598708
599709 If "nested" is True, this is a nested iterator such as "e" in "enumerate(e)".
710+
711+ "reassigned" contains the local variables that the loop may assign to
712+ (see accept_loop_state).
600713 """
601714
602715 # Do an async loop if needed. async is always generic
@@ -611,7 +724,7 @@ def make_for_loop_generator(
611724 rtyp = builder .node_type (expr )
612725 if is_sequence_rprimitive (rtyp ) or isinstance (rtyp , RVec ):
613726 # Special case "for x in <seq>" for concrete sequence types.
614- expr_reg = builder . accept ( expr )
727+ expr_reg = accept_loop_state ( builder , expr , reassigned )
615728 target_type = builder .get_sequence_type (expr )
616729
617730 for_list = ForSequence (builder , index , body_block , loop_exit , line , nested )
@@ -620,7 +733,7 @@ def make_for_loop_generator(
620733
621734 if is_dict_rprimitive (rtyp ):
622735 # Special case "for k in <dict>".
623- expr_reg = builder . accept ( expr )
736+ expr_reg = accept_loop_state ( builder , expr , reassigned )
624737 target_type = builder .get_dict_key_type (expr )
625738
626739 for_dict = ForDictionaryKeys (builder , index , body_block , loop_exit , line , nested )
@@ -642,10 +755,10 @@ def make_for_loop_generator(
642755 # direction of comparison to do.
643756 if len (expr .args ) == 1 :
644757 start_reg : Value = Integer (0 )
645- end_reg = builder . accept ( expr .args [0 ])
758+ end_reg = accept_loop_state ( builder , expr .args [0 ], reassigned )
646759 else :
647760 start_reg = builder .accept (expr .args [0 ])
648- end_reg = builder . accept ( expr .args [1 ])
761+ end_reg = accept_loop_state ( builder , expr .args [1 ], reassigned )
649762 if len (expr .args ) == 3 :
650763 step = builder .extract_int (expr .args [2 ])
651764 assert step is not None
@@ -669,7 +782,7 @@ def make_for_loop_generator(
669782 lvalue1 = index .items [0 ]
670783 lvalue2 = index .items [1 ]
671784 for_enumerate = ForEnumerate (builder , index , body_block , loop_exit , line , nested )
672- for_enumerate .init (lvalue1 , lvalue2 , expr .args [0 ])
785+ for_enumerate .init (lvalue1 , lvalue2 , expr .args [0 ], reassigned )
673786 return for_enumerate
674787
675788 elif (
@@ -681,7 +794,7 @@ def make_for_loop_generator(
681794 ):
682795 # Special case "for x, y in zip(a, b)".
683796 for_zip = ForZip (builder , index , body_block , loop_exit , line , nested )
684- for_zip .init (index .items , expr .args )
797+ for_zip .init (index .items , expr .args , reassigned )
685798 return for_zip
686799
687800 if (
@@ -691,7 +804,7 @@ def make_for_loop_generator(
691804 and is_sequence_rprimitive (builder .node_type (expr .args [0 ]))
692805 ):
693806 # Special case "for x in reversed(<list>)".
694- expr_reg = builder . accept ( expr .args [0 ])
807+ expr_reg = accept_loop_state ( builder , expr .args [0 ], reassigned )
695808 target_type = builder .get_sequence_type (expr )
696809
697810 for_list = ForSequence (builder , index , body_block , loop_exit , line , nested )
@@ -701,7 +814,7 @@ def make_for_loop_generator(
701814 # Special cases for dictionary iterator methods, like dict.items().
702815 rtype = builder .node_type (expr .callee .expr )
703816 if is_dict_rprimitive (rtype ) and expr .callee .name in ("keys" , "values" , "items" ):
704- expr_reg = builder . accept ( expr .callee .expr )
817+ expr_reg = accept_loop_state ( builder , expr .callee .expr , reassigned )
705818 for_dict_type : type [ForGenerator ] | None = None
706819 if expr .callee .name == "keys" :
707820 target_type = builder .get_dict_key_type (expr .callee .expr )
@@ -1362,15 +1475,28 @@ def need_cleanup(self) -> bool:
13621475 # redundant cleanup block, but that's okay.
13631476 return True
13641477
1365- def init (self , index1 : Lvalue , index2 : Lvalue , expr : Expression ) -> None :
1478+ def init (
1479+ self ,
1480+ index1 : Lvalue ,
1481+ index2 : Lvalue ,
1482+ expr : Expression ,
1483+ reassigned : AbstractSet [SymbolNode ] = frozenset (),
1484+ ) -> None :
13661485 # Count from 0 to infinity (for the index lvalue).
13671486 self .index_gen = ForInfiniteCounter (
13681487 self .builder , index1 , self .body_block , self .loop_exit , self .line , nested = True
13691488 )
13701489 self .index_gen .init ()
13711490 # Iterate over the actual iterable.
13721491 self .main_gen = make_for_loop_generator (
1373- self .builder , index2 , expr , self .body_block , self .loop_exit , self .line , nested = True
1492+ self .builder ,
1493+ index2 ,
1494+ expr ,
1495+ self .body_block ,
1496+ self .loop_exit ,
1497+ self .line ,
1498+ nested = True ,
1499+ reassigned = reassigned ,
13741500 )
13751501
13761502 def gen_condition (self ) -> None :
@@ -1398,15 +1524,27 @@ def need_cleanup(self) -> bool:
13981524 # redundant cleanup block, but that's okay.
13991525 return True
14001526
1401- def init (self , indexes : list [Lvalue ], exprs : list [Expression ]) -> None :
1527+ def init (
1528+ self ,
1529+ indexes : list [Lvalue ],
1530+ exprs : list [Expression ],
1531+ reassigned : AbstractSet [SymbolNode ] = frozenset (),
1532+ ) -> None :
14021533 assert len (indexes ) == len (exprs )
14031534 # Condition check will require multiple basic blocks, since there will be
14041535 # multiple conditions to check.
14051536 self .cond_blocks = [BasicBlock () for _ in range (len (indexes ) - 1 )] + [self .body_block ]
14061537 self .gens : list [ForGenerator ] = []
14071538 for index , expr , next_block in zip (indexes , exprs , self .cond_blocks ):
14081539 gen = make_for_loop_generator (
1409- self .builder , index , expr , next_block , self .loop_exit , self .line , nested = True
1540+ self .builder ,
1541+ index ,
1542+ expr ,
1543+ next_block ,
1544+ self .loop_exit ,
1545+ self .line ,
1546+ nested = True ,
1547+ reassigned = reassigned ,
14101548 )
14111549 self .gens .append (gen )
14121550
0 commit comments