2020 TupleExpr ,
2121 TypeAlias ,
2222)
23- from mypyc .ir .ops import BasicBlock , Branch , Integer , IntOp , Register , TupleGet , TupleSet , Value
23+ from mypyc .ir .ops import (
24+ BasicBlock ,
25+ Branch ,
26+ Integer ,
27+ IntOp ,
28+ LoadAddress ,
29+ LoadMem ,
30+ Register ,
31+ TupleGet ,
32+ TupleSet ,
33+ Value ,
34+ )
2435from mypyc .ir .rtypes import (
2536 RTuple ,
2637 RType ,
38+ bool_rprimitive ,
2739 int_rprimitive ,
2840 is_dict_rprimitive ,
2941 is_list_rprimitive ,
3042 is_sequence_rprimitive ,
3143 is_short_int_rprimitive ,
3244 is_str_rprimitive ,
3345 is_tuple_rprimitive ,
46+ pointer_rprimitive ,
3447 short_int_rprimitive ,
3548)
3649from mypyc .irbuild .builder import IRBuilder
4558 dict_value_iter_op ,
4659)
4760from mypyc .primitives .exc_ops import no_err_occurred_op
48- from mypyc .primitives .generic_ops import iter_op , next_op
61+ from mypyc .primitives .generic_ops import aiter_op , anext_op , iter_op , next_op
4962from mypyc .primitives .list_ops import list_append_op , list_get_item_unsafe_op , new_list_set_item_op
63+ from mypyc .primitives .misc_ops import stop_async_iteration_op
5064from mypyc .primitives .registry import CFunctionDescription
5165from mypyc .primitives .set_ops import set_add_op
5266
@@ -59,6 +73,7 @@ def for_loop_helper(
5973 expr : Expression ,
6074 body_insts : GenFunc ,
6175 else_insts : GenFunc | None ,
76+ is_async : bool ,
6277 line : int ,
6378) -> None :
6479 """Generate IR for a loop.
@@ -81,7 +96,9 @@ def for_loop_helper(
8196 # Determine where we want to exit, if our condition check fails.
8297 normal_loop_exit = else_block if else_insts is not None else exit_block
8398
84- for_gen = make_for_loop_generator (builder , index , expr , body_block , normal_loop_exit , line )
99+ for_gen = make_for_loop_generator (
100+ builder , index , expr , body_block , normal_loop_exit , line , is_async = is_async
101+ )
85102
86103 builder .push_loop_stack (step_block , exit_block )
87104 condition_block = BasicBlock ()
@@ -220,32 +237,33 @@ def translate_list_comprehension(builder: IRBuilder, gen: GeneratorExpr) -> Valu
220237 if val is not None :
221238 return val
222239
223- list_ops = builder .new_list_op ([], gen .line )
224- loop_params = list (zip (gen .indices , gen .sequences , gen .condlists ))
240+ list_ops = builder .maybe_spill (builder .new_list_op ([], gen .line ))
241+
242+ loop_params = list (zip (gen .indices , gen .sequences , gen .condlists , gen .is_async ))
225243
226244 def gen_inner_stmts () -> None :
227245 e = builder .accept (gen .left_expr )
228- builder .call_c (list_append_op , [list_ops , e ], gen .line )
246+ builder .call_c (list_append_op , [builder . read ( list_ops ) , e ], gen .line )
229247
230248 comprehension_helper (builder , loop_params , gen_inner_stmts , gen .line )
231- return list_ops
249+ return builder . read ( list_ops )
232250
233251
234252def translate_set_comprehension (builder : IRBuilder , gen : GeneratorExpr ) -> Value :
235- set_ops = builder .new_set_op ([], gen .line )
236- loop_params = list (zip (gen .indices , gen .sequences , gen .condlists ))
253+ set_ops = builder .maybe_spill ( builder . new_set_op ([], gen .line ) )
254+ loop_params = list (zip (gen .indices , gen .sequences , gen .condlists , gen . is_async ))
237255
238256 def gen_inner_stmts () -> None :
239257 e = builder .accept (gen .left_expr )
240- builder .call_c (set_add_op , [set_ops , e ], gen .line )
258+ builder .call_c (set_add_op , [builder . read ( set_ops ) , e ], gen .line )
241259
242260 comprehension_helper (builder , loop_params , gen_inner_stmts , gen .line )
243- return set_ops
261+ return builder . read ( set_ops )
244262
245263
246264def comprehension_helper (
247265 builder : IRBuilder ,
248- loop_params : list [tuple [Lvalue , Expression , list [Expression ]]],
266+ loop_params : list [tuple [Lvalue , Expression , list [Expression ], bool ]],
249267 gen_inner_stmts : Callable [[], None ],
250268 line : int ,
251269) -> None :
@@ -260,20 +278,26 @@ def comprehension_helper(
260278 gen_inner_stmts: function to generate the IR for the body of the innermost loop
261279 """
262280
263- def handle_loop (loop_params : list [tuple [Lvalue , Expression , list [Expression ]]]) -> None :
281+ def handle_loop (loop_params : list [tuple [Lvalue , Expression , list [Expression ], bool ]]) -> None :
264282 """Generate IR for a loop.
265283
266284 Given a list of (index, expression, [conditions]) tuples, generate IR
267285 for the nested loops the list defines.
268286 """
269- index , expr , conds = loop_params [0 ]
287+ index , expr , conds , is_async = loop_params [0 ]
270288 for_loop_helper (
271- builder , index , expr , lambda : loop_contents (conds , loop_params [1 :]), None , line
289+ builder ,
290+ index ,
291+ expr ,
292+ lambda : loop_contents (conds , loop_params [1 :]),
293+ None ,
294+ is_async = is_async ,
295+ line = line ,
272296 )
273297
274298 def loop_contents (
275299 conds : list [Expression ],
276- remaining_loop_params : list [tuple [Lvalue , Expression , list [Expression ]]],
300+ remaining_loop_params : list [tuple [Lvalue , Expression , list [Expression ], bool ]],
277301 ) -> None :
278302 """Generate the body of the loop.
279303
@@ -319,13 +343,23 @@ def make_for_loop_generator(
319343 body_block : BasicBlock ,
320344 loop_exit : BasicBlock ,
321345 line : int ,
346+ is_async : bool = False ,
322347 nested : bool = False ,
323348) -> ForGenerator :
324349 """Return helper object for generating a for loop over an iterable.
325350
326351 If "nested" is True, this is a nested iterator such as "e" in "enumerate(e)".
327352 """
328353
354+ # Do an async loop if needed. async is always generic
355+ if is_async :
356+ expr_reg = builder .accept (expr )
357+ async_obj = ForAsyncIterable (builder , index , body_block , loop_exit , line , nested )
358+ item_type = builder ._analyze_iterable_item_type (expr )
359+ item_rtype = builder .type_to_rtype (item_type )
360+ async_obj .init (expr_reg , item_rtype )
361+ return async_obj
362+
329363 rtyp = builder .node_type (expr )
330364 if is_sequence_rprimitive (rtyp ):
331365 # Special case "for x in <list>".
@@ -500,7 +534,7 @@ def load_len(self, expr: Value | AssignmentTarget) -> Value:
500534
501535
502536class ForIterable (ForGenerator ):
503- """Generate IR for a for loop over an arbitrary iterable (the normal case)."""
537+ """Generate IR for a for loop over an arbitrary iterable (the general case)."""
504538
505539 def need_cleanup (self ) -> bool :
506540 # Create a new cleanup block for when the loop is finished.
@@ -548,6 +582,70 @@ def gen_cleanup(self) -> None:
548582 self .builder .call_c (no_err_occurred_op , [], self .line )
549583
550584
585+ class ForAsyncIterable (ForGenerator ):
586+ """Generate IR for an async for loop."""
587+
588+ def init (self , expr_reg : Value , target_type : RType ) -> None :
589+ # Define targets to contain the expression, along with the
590+ # iterator that will be used for the for-loop. We are inside
591+ # of a generator function, so we will spill these into
592+ # environment class.
593+ builder = self .builder
594+ iter_reg = builder .call_c (aiter_op , [expr_reg ], self .line )
595+ builder .maybe_spill (expr_reg )
596+ self .iter_target = builder .maybe_spill (iter_reg )
597+ self .target_type = target_type
598+ self .stop_reg = Register (bool_rprimitive )
599+
600+ def gen_condition (self ) -> None :
601+ # This does the test and fetches the next value
602+ # try:
603+ # TARGET = await type(iter).__anext__(iter)
604+ # stop = False
605+ # except StopAsyncIteration:
606+ # stop = True
607+ #
608+ # What a pain.
609+ # There are optimizations available here if we punch through some abstractions.
610+
611+ from mypyc .irbuild .statement import emit_await , transform_try_except
612+
613+ builder = self .builder
614+ line = self .line
615+
616+ def except_match () -> Value :
617+ addr = builder .add (LoadAddress (pointer_rprimitive , stop_async_iteration_op .src , line ))
618+ return builder .add (LoadMem (stop_async_iteration_op .type , addr ))
619+
620+ def try_body () -> None :
621+ awaitable = builder .call_c (anext_op , [builder .read (self .iter_target )], line )
622+ self .next_reg = emit_await (builder , awaitable , line )
623+ builder .assign (self .stop_reg , builder .false (), - 1 )
624+
625+ def except_body () -> None :
626+ builder .assign (self .stop_reg , builder .true (), line )
627+
628+ transform_try_except (
629+ builder , try_body , [((except_match , line ), None , except_body )], None , line
630+ )
631+
632+ builder .add (Branch (self .stop_reg , self .loop_exit , self .body_block , Branch .BOOL ))
633+
634+ def begin_body (self ) -> None :
635+ # Assign the value obtained from await __anext__ to the
636+ # lvalue so that it can be referenced by code in the body of the loop.
637+ builder = self .builder
638+ line = self .line
639+ # We unbox here so that iterating with tuple unpacking generates a tuple based
640+ # unpack instead of an iterator based one.
641+ next_reg = builder .coerce (self .next_reg , self .target_type , line )
642+ builder .assign (builder .get_assignment_target (self .index ), next_reg , line )
643+
644+ def gen_step (self ) -> None :
645+ # Nothing to do here, since we get the next item as part of gen_condition().
646+ pass
647+
648+
551649def unsafe_index (builder : IRBuilder , target : Value , index : Value , line : int ) -> Value :
552650 """Emit a potentially unsafe index into a target."""
553651 # This doesn't really fit nicely into any of our data-driven frameworks
0 commit comments