20
20
TupleExpr ,
21
21
TypeAlias ,
22
22
)
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
+ )
24
35
from mypyc .ir .rtypes import (
25
36
RTuple ,
26
37
RType ,
38
+ bool_rprimitive ,
27
39
int_rprimitive ,
28
40
is_dict_rprimitive ,
29
41
is_list_rprimitive ,
30
42
is_sequence_rprimitive ,
31
43
is_short_int_rprimitive ,
32
44
is_str_rprimitive ,
33
45
is_tuple_rprimitive ,
46
+ pointer_rprimitive ,
34
47
short_int_rprimitive ,
35
48
)
36
49
from mypyc .irbuild .builder import IRBuilder
45
58
dict_value_iter_op ,
46
59
)
47
60
from 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
49
62
from 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
50
64
from mypyc .primitives .registry import CFunctionDescription
51
65
from mypyc .primitives .set_ops import set_add_op
52
66
@@ -59,6 +73,7 @@ def for_loop_helper(
59
73
expr : Expression ,
60
74
body_insts : GenFunc ,
61
75
else_insts : GenFunc | None ,
76
+ is_async : bool ,
62
77
line : int ,
63
78
) -> None :
64
79
"""Generate IR for a loop.
@@ -81,7 +96,9 @@ def for_loop_helper(
81
96
# Determine where we want to exit, if our condition check fails.
82
97
normal_loop_exit = else_block if else_insts is not None else exit_block
83
98
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
+ )
85
102
86
103
builder .push_loop_stack (step_block , exit_block )
87
104
condition_block = BasicBlock ()
@@ -220,32 +237,33 @@ def translate_list_comprehension(builder: IRBuilder, gen: GeneratorExpr) -> Valu
220
237
if val is not None :
221
238
return val
222
239
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 ))
225
243
226
244
def gen_inner_stmts () -> None :
227
245
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 )
229
247
230
248
comprehension_helper (builder , loop_params , gen_inner_stmts , gen .line )
231
- return list_ops
249
+ return builder . read ( list_ops )
232
250
233
251
234
252
def 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 ))
237
255
238
256
def gen_inner_stmts () -> None :
239
257
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 )
241
259
242
260
comprehension_helper (builder , loop_params , gen_inner_stmts , gen .line )
243
- return set_ops
261
+ return builder . read ( set_ops )
244
262
245
263
246
264
def comprehension_helper (
247
265
builder : IRBuilder ,
248
- loop_params : list [tuple [Lvalue , Expression , list [Expression ]]],
266
+ loop_params : list [tuple [Lvalue , Expression , list [Expression ], bool ]],
249
267
gen_inner_stmts : Callable [[], None ],
250
268
line : int ,
251
269
) -> None :
@@ -260,20 +278,26 @@ def comprehension_helper(
260
278
gen_inner_stmts: function to generate the IR for the body of the innermost loop
261
279
"""
262
280
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 :
264
282
"""Generate IR for a loop.
265
283
266
284
Given a list of (index, expression, [conditions]) tuples, generate IR
267
285
for the nested loops the list defines.
268
286
"""
269
- index , expr , conds = loop_params [0 ]
287
+ index , expr , conds , is_async = loop_params [0 ]
270
288
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 ,
272
296
)
273
297
274
298
def loop_contents (
275
299
conds : list [Expression ],
276
- remaining_loop_params : list [tuple [Lvalue , Expression , list [Expression ]]],
300
+ remaining_loop_params : list [tuple [Lvalue , Expression , list [Expression ], bool ]],
277
301
) -> None :
278
302
"""Generate the body of the loop.
279
303
@@ -319,13 +343,23 @@ def make_for_loop_generator(
319
343
body_block : BasicBlock ,
320
344
loop_exit : BasicBlock ,
321
345
line : int ,
346
+ is_async : bool = False ,
322
347
nested : bool = False ,
323
348
) -> ForGenerator :
324
349
"""Return helper object for generating a for loop over an iterable.
325
350
326
351
If "nested" is True, this is a nested iterator such as "e" in "enumerate(e)".
327
352
"""
328
353
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
+
329
363
rtyp = builder .node_type (expr )
330
364
if is_sequence_rprimitive (rtyp ):
331
365
# Special case "for x in <list>".
@@ -500,7 +534,7 @@ def load_len(self, expr: Value | AssignmentTarget) -> Value:
500
534
501
535
502
536
class 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)."""
504
538
505
539
def need_cleanup (self ) -> bool :
506
540
# Create a new cleanup block for when the loop is finished.
@@ -548,6 +582,70 @@ def gen_cleanup(self) -> None:
548
582
self .builder .call_c (no_err_occurred_op , [], self .line )
549
583
550
584
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
+
551
649
def unsafe_index (builder : IRBuilder , target : Value , index : Value , line : int ) -> Value :
552
650
"""Emit a potentially unsafe index into a target."""
553
651
# This doesn't really fit nicely into any of our data-driven frameworks
0 commit comments