4848 int_rprimitive ,
4949 is_dict_rprimitive ,
5050 is_fixed_width_rtype ,
51+ is_immutable_rprimitive ,
5152 is_list_rprimitive ,
5253 is_sequence_rprimitive ,
5354 is_short_int_rprimitive ,
@@ -205,9 +206,9 @@ def sequence_from_generator_preallocate_helper(
205206 there is no condition list in the generator and only one original sequence with
206207 one index is allowed.
207208
208- e.g. (1) tuple(f(x) for x in a_list/a_tuple)
209- (2) list(f(x) for x in a_list/a_tuple)
210- (3) [f(x) for x in a_list/a_tuple]
209+ e.g. (1) tuple(f(x) for x in a_list/a_tuple/a_str/a_bytes )
210+ (2) list(f(x) for x in a_list/a_tuple/a_str/a_bytes )
211+ (3) [f(x) for x in a_list/a_tuple/a_str/a_bytes ]
211212 RTuple as an original sequence is not supported yet.
212213
213214 Args:
@@ -224,7 +225,7 @@ def sequence_from_generator_preallocate_helper(
224225 """
225226 if len (gen .sequences ) == 1 and len (gen .indices ) == 1 and len (gen .condlists [0 ]) == 0 :
226227 rtype = builder .node_type (gen .sequences [0 ])
227- if is_list_rprimitive ( rtype ) or is_tuple_rprimitive ( rtype ) or is_str_rprimitive (rtype ):
228+ if is_sequence_rprimitive (rtype ):
228229 sequence = builder .accept (gen .sequences [0 ])
229230 length = builder .builder .builtin_len (sequence , gen .line , use_pyssize_t = True )
230231 target_op = empty_op_llbuilder (length , gen .line )
@@ -785,17 +786,31 @@ class ForSequence(ForGenerator):
785786 Supports iterating in both forward and reverse.
786787 """
787788
789+ length_reg : Value | AssignmentTarget | None
790+
788791 def init (self , expr_reg : Value , target_type : RType , reverse : bool ) -> None :
792+ assert is_sequence_rprimitive (expr_reg .type ), expr_reg
789793 builder = self .builder
790794 self .reverse = reverse
791795 # Define target to contain the expression, along with the index that will be used
792796 # for the for-loop. If we are inside of a generator function, spill these into the
793797 # environment class.
794798 self .expr_target = builder .maybe_spill (expr_reg )
799+ if is_immutable_rprimitive (expr_reg .type ):
800+ # If the expression is an immutable type, we can load the length just once.
801+ self .length_reg = builder .maybe_spill (self .load_len (self .expr_target ))
802+ else :
803+ # Otherwise, even if the length is known, we must recalculate the length
804+ # at every iteration for compatibility with python semantics.
805+ self .length_reg = None
795806 if not reverse :
796807 index_reg : Value = Integer (0 , c_pyssize_t_rprimitive )
797808 else :
798- index_reg = builder .builder .int_sub (self .load_len (self .expr_target ), 1 )
809+ if self .length_reg is not None :
810+ len_val = builder .read (self .length_reg )
811+ else :
812+ len_val = self .load_len (self .expr_target )
813+ index_reg = builder .builder .int_sub (len_val , 1 )
799814 self .index_target = builder .maybe_spill_assignable (index_reg )
800815 self .target_type = target_type
801816
@@ -814,9 +829,13 @@ def gen_condition(self) -> None:
814829 second_check = BasicBlock ()
815830 builder .add_bool_branch (comparison , second_check , self .loop_exit )
816831 builder .activate_block (second_check )
817- # For compatibility with python semantics we recalculate the length
818- # at every iteration.
819- len_reg = self .load_len (self .expr_target )
832+ if self .length_reg is None :
833+ # For compatibility with python semantics we recalculate the length
834+ # at every iteration.
835+ len_reg = self .load_len (self .expr_target )
836+ else :
837+ # (unless input is immutable type).
838+ len_reg = builder .read (self .length_reg , line )
820839 comparison = builder .binary_op (builder .read (self .index_target , line ), len_reg , "<" , line )
821840 builder .add_bool_branch (comparison , self .body_block , self .loop_exit )
822841
0 commit comments