77
88from __future__ import annotations
99
10- from collections .abc import Callable
10+ from collections .abc import Callable , Set as AbstractSet
1111from typing import ClassVar , cast
1212
1313from mypy .nodes import (
1414 ARG_POS ,
15+ AssignmentExpr ,
16+ AssignmentStmt ,
1517 CallExpr ,
18+ DelStmt ,
1619 DictionaryComprehension ,
1720 Expression ,
21+ ForStmt ,
1822 GeneratorExpr ,
1923 ListExpr ,
2024 Lvalue ,
2125 MemberExpr ,
2226 NameExpr ,
27+ OperatorAssignmentStmt ,
2328 RefExpr ,
2429 SetExpr ,
2530 StarExpr ,
31+ SymbolNode ,
32+ TryStmt ,
2633 TupleExpr ,
2734 TypeAlias ,
2835 Var ,
36+ WithStmt ,
2937)
38+ from mypy .patterns import AsPattern , MappingPattern , StarredPattern
39+ from mypy .traverser import TraverserVisitor
3040from mypy .types import LiteralType , TupleType , get_proper_type , get_proper_types
3141from mypyc .common import GENERATOR_HELPER_NAME , IS_FREE_THREADED
3242from mypyc .ir .ops import (
7080)
7181from mypyc .irbuild .builder import IRBuilder
7282from mypyc .irbuild .constant_fold import constant_fold_expr
73- from mypyc .irbuild .targets import AssignmentTargetTuple
83+ from mypyc .irbuild .targets import AssignmentTargetRegister , AssignmentTargetTuple
7484from mypyc .irbuild .vec import vec_append , vec_create , vec_get_item_unsafe , vec_init_item_unsafe
7585from mypyc .primitives .dict_ops import (
7686 dict_check_size_op ,
@@ -106,6 +116,7 @@ def for_loop_helper(
106116 else_insts : GenFunc | None ,
107117 is_async : bool ,
108118 line : int ,
119+ reassigned : AbstractSet [SymbolNode ] = frozenset (),
109120) -> None :
110121 """Generate IR for a loop.
111122
@@ -114,6 +125,8 @@ def for_loop_helper(
114125 expr: the expression to iterate over
115126 body_insts: a function that generates the body of the loop
116127 else_insts: a function that generates the else block instructions
128+ reassigned: local variables that the loop index or body may assign to
129+ (see find_loop_assignments)
117130 """
118131 # Body of the loop
119132 body_block = BasicBlock ()
@@ -128,7 +141,14 @@ def for_loop_helper(
128141 normal_loop_exit = else_block if else_insts is not None else exit_block
129142
130143 for_gen = make_for_loop_generator (
131- builder , index , expr , body_block , normal_loop_exit , line , is_async = is_async
144+ builder ,
145+ index ,
146+ expr ,
147+ body_block ,
148+ normal_loop_exit ,
149+ line ,
150+ is_async = is_async ,
151+ reassigned = reassigned ,
132152 )
133153
134154 builder .push_loop_stack (step_block , exit_block )
@@ -479,6 +499,95 @@ def is_range_ref(expr: RefExpr) -> bool:
479499 )
480500
481501
502+ def find_loop_assignments (s : ForStmt ) -> set [SymbolNode ]:
503+ """Return the variables that the index or the body of a for loop may assign to."""
504+ collector = AssignmentCollector ()
505+ collector .add_lvalue (s .index )
506+ s .body .accept (collector )
507+ return collector .symbols
508+
509+
510+ class AssignmentCollector (TraverserVisitor ):
511+ """Collect the symbols of the variables assigned to (or deleted) in a subtree."""
512+
513+ def __init__ (self ) -> None :
514+ super ().__init__ ()
515+ self .symbols : set [SymbolNode ] = set ()
516+
517+ def add_lvalue (self , lvalue : Expression | None ) -> None :
518+ if isinstance (lvalue , NameExpr ):
519+ if lvalue .node is not None :
520+ self .symbols .add (lvalue .node )
521+ elif isinstance (lvalue , (TupleExpr , ListExpr )):
522+ for item in lvalue .items :
523+ self .add_lvalue (item )
524+ elif isinstance (lvalue , StarExpr ):
525+ self .add_lvalue (lvalue .expr )
526+
527+ def visit_assignment_stmt (self , o : AssignmentStmt ) -> None :
528+ for lvalue in o .lvalues :
529+ self .add_lvalue (lvalue )
530+ super ().visit_assignment_stmt (o )
531+
532+ def visit_operator_assignment_stmt (self , o : OperatorAssignmentStmt ) -> None :
533+ self .add_lvalue (o .lvalue )
534+ super ().visit_operator_assignment_stmt (o )
535+
536+ def visit_assignment_expr (self , o : AssignmentExpr ) -> None :
537+ self .add_lvalue (o .target )
538+ super ().visit_assignment_expr (o )
539+
540+ def visit_for_stmt (self , o : ForStmt ) -> None :
541+ self .add_lvalue (o .index )
542+ super ().visit_for_stmt (o )
543+
544+ def visit_with_stmt (self , o : WithStmt ) -> None :
545+ for target in o .target :
546+ self .add_lvalue (target )
547+ super ().visit_with_stmt (o )
548+
549+ def visit_try_stmt (self , o : TryStmt ) -> None :
550+ for var in o .vars :
551+ self .add_lvalue (var )
552+ super ().visit_try_stmt (o )
553+
554+ def visit_del_stmt (self , o : DelStmt ) -> None :
555+ self .add_lvalue (o .expr )
556+ super ().visit_del_stmt (o )
557+
558+ def visit_as_pattern (self , o : AsPattern ) -> None :
559+ self .add_lvalue (o .name )
560+ super ().visit_as_pattern (o )
561+
562+ def visit_starred_pattern (self , o : StarredPattern ) -> None :
563+ self .add_lvalue (o .capture )
564+ super ().visit_starred_pattern (o )
565+
566+ def visit_mapping_pattern (self , o : MappingPattern ) -> None :
567+ self .add_lvalue (o .rest )
568+ super ().visit_mapping_pattern (o )
569+
570+
571+ def accept_loop_state (
572+ builder : IRBuilder , expr : Expression , reassigned : AbstractSet [SymbolNode ]
573+ ) -> Value :
574+ """Evaluate the object to iterate over, or the end of a range.
575+
576+ Specialized loops read this value again on every iteration. If it's the register
577+ of a local variable that the loop may assign to, copy it to a new register, since
578+ the assignment must not affect the iteration (as in "for x in a: a = []").
579+ """
580+ value = builder .accept (expr )
581+ if isinstance (value , Register ):
582+ for symbol in reassigned :
583+ target = builder .symtables [- 1 ].get (symbol )
584+ if isinstance (target , AssignmentTargetRegister ) and target .register is value :
585+ copy = Register (value .type )
586+ builder .assign (copy , value , expr .line )
587+ return copy
588+ return value
589+
590+
482591def make_for_loop_generator (
483592 builder : IRBuilder ,
484593 index : Lvalue ,
@@ -488,10 +597,14 @@ def make_for_loop_generator(
488597 line : int ,
489598 is_async : bool = False ,
490599 nested : bool = False ,
600+ reassigned : AbstractSet [SymbolNode ] = frozenset (),
491601) -> ForGenerator :
492602 """Return helper object for generating a for loop over an iterable.
493603
494604 If "nested" is True, this is a nested iterator such as "e" in "enumerate(e)".
605+
606+ "reassigned" contains the local variables that the loop may assign to
607+ (see accept_loop_state).
495608 """
496609
497610 # Do an async loop if needed. async is always generic
@@ -506,7 +619,7 @@ def make_for_loop_generator(
506619 rtyp = builder .node_type (expr )
507620 if is_sequence_rprimitive (rtyp ) or isinstance (rtyp , RVec ):
508621 # Special case "for x in <seq>" for concrete sequence types.
509- expr_reg = builder . accept ( expr )
622+ expr_reg = accept_loop_state ( builder , expr , reassigned )
510623 target_type = builder .get_sequence_type (expr )
511624
512625 for_list = ForSequence (builder , index , body_block , loop_exit , line , nested )
@@ -515,7 +628,7 @@ def make_for_loop_generator(
515628
516629 if is_dict_rprimitive (rtyp ):
517630 # Special case "for k in <dict>".
518- expr_reg = builder . accept ( expr )
631+ expr_reg = accept_loop_state ( builder , expr , reassigned )
519632 target_type = builder .get_dict_key_type (expr )
520633
521634 for_dict = ForDictionaryKeys (builder , index , body_block , loop_exit , line , nested )
@@ -537,10 +650,10 @@ def make_for_loop_generator(
537650 # direction of comparison to do.
538651 if len (expr .args ) == 1 :
539652 start_reg : Value = Integer (0 )
540- end_reg = builder . accept ( expr .args [0 ])
653+ end_reg = accept_loop_state ( builder , expr .args [0 ], reassigned )
541654 else :
542655 start_reg = builder .accept (expr .args [0 ])
543- end_reg = builder . accept ( expr .args [1 ])
656+ end_reg = accept_loop_state ( builder , expr .args [1 ], reassigned )
544657 if len (expr .args ) == 3 :
545658 step = builder .extract_int (expr .args [2 ])
546659 assert step is not None
@@ -564,7 +677,7 @@ def make_for_loop_generator(
564677 lvalue1 = index .items [0 ]
565678 lvalue2 = index .items [1 ]
566679 for_enumerate = ForEnumerate (builder , index , body_block , loop_exit , line , nested )
567- for_enumerate .init (lvalue1 , lvalue2 , expr .args [0 ])
680+ for_enumerate .init (lvalue1 , lvalue2 , expr .args [0 ], reassigned )
568681 return for_enumerate
569682
570683 elif (
@@ -576,7 +689,7 @@ def make_for_loop_generator(
576689 ):
577690 # Special case "for x, y in zip(a, b)".
578691 for_zip = ForZip (builder , index , body_block , loop_exit , line , nested )
579- for_zip .init (index .items , expr .args )
692+ for_zip .init (index .items , expr .args , reassigned )
580693 return for_zip
581694
582695 if (
@@ -586,7 +699,7 @@ def make_for_loop_generator(
586699 and is_sequence_rprimitive (builder .node_type (expr .args [0 ]))
587700 ):
588701 # Special case "for x in reversed(<list>)".
589- expr_reg = builder . accept ( expr .args [0 ])
702+ expr_reg = accept_loop_state ( builder , expr .args [0 ], reassigned )
590703 target_type = builder .get_sequence_type (expr )
591704
592705 for_list = ForSequence (builder , index , body_block , loop_exit , line , nested )
@@ -596,7 +709,7 @@ def make_for_loop_generator(
596709 # Special cases for dictionary iterator methods, like dict.items().
597710 rtype = builder .node_type (expr .callee .expr )
598711 if is_dict_rprimitive (rtype ) and expr .callee .name in ("keys" , "values" , "items" ):
599- expr_reg = builder . accept ( expr .callee .expr )
712+ expr_reg = accept_loop_state ( builder , expr .callee .expr , reassigned )
600713 for_dict_type : type [ForGenerator ] | None = None
601714 if expr .callee .name == "keys" :
602715 target_type = builder .get_dict_key_type (expr .callee .expr )
@@ -1257,15 +1370,28 @@ def need_cleanup(self) -> bool:
12571370 # redundant cleanup block, but that's okay.
12581371 return True
12591372
1260- def init (self , index1 : Lvalue , index2 : Lvalue , expr : Expression ) -> None :
1373+ def init (
1374+ self ,
1375+ index1 : Lvalue ,
1376+ index2 : Lvalue ,
1377+ expr : Expression ,
1378+ reassigned : AbstractSet [SymbolNode ] = frozenset (),
1379+ ) -> None :
12611380 # Count from 0 to infinity (for the index lvalue).
12621381 self .index_gen = ForInfiniteCounter (
12631382 self .builder , index1 , self .body_block , self .loop_exit , self .line , nested = True
12641383 )
12651384 self .index_gen .init ()
12661385 # Iterate over the actual iterable.
12671386 self .main_gen = make_for_loop_generator (
1268- self .builder , index2 , expr , self .body_block , self .loop_exit , self .line , nested = True
1387+ self .builder ,
1388+ index2 ,
1389+ expr ,
1390+ self .body_block ,
1391+ self .loop_exit ,
1392+ self .line ,
1393+ nested = True ,
1394+ reassigned = reassigned ,
12691395 )
12701396
12711397 def gen_condition (self ) -> None :
@@ -1293,15 +1419,27 @@ def need_cleanup(self) -> bool:
12931419 # redundant cleanup block, but that's okay.
12941420 return True
12951421
1296- def init (self , indexes : list [Lvalue ], exprs : list [Expression ]) -> None :
1422+ def init (
1423+ self ,
1424+ indexes : list [Lvalue ],
1425+ exprs : list [Expression ],
1426+ reassigned : AbstractSet [SymbolNode ] = frozenset (),
1427+ ) -> None :
12971428 assert len (indexes ) == len (exprs )
12981429 # Condition check will require multiple basic blocks, since there will be
12991430 # multiple conditions to check.
13001431 self .cond_blocks = [BasicBlock () for _ in range (len (indexes ) - 1 )] + [self .body_block ]
13011432 self .gens : list [ForGenerator ] = []
13021433 for index , expr , next_block in zip (indexes , exprs , self .cond_blocks ):
13031434 gen = make_for_loop_generator (
1304- self .builder , index , expr , next_block , self .loop_exit , self .line , nested = True
1435+ self .builder ,
1436+ index ,
1437+ expr ,
1438+ next_block ,
1439+ self .loop_exit ,
1440+ self .line ,
1441+ nested = True ,
1442+ reassigned = reassigned ,
13051443 )
13061444 self .gens .append (gen )
13071445
0 commit comments