|
24 | 24 | TypeAlias, |
25 | 25 | ) |
26 | 26 | from mypyc.ir.ops import ( |
| 27 | + ERR_NEVER, |
27 | 28 | BasicBlock, |
28 | 29 | Branch, |
29 | 30 | Integer, |
30 | 31 | IntOp, |
31 | 32 | LoadAddress, |
| 33 | + LoadErrorValue, |
32 | 34 | LoadMem, |
| 35 | + MethodCall, |
33 | 36 | RaiseStandardError, |
34 | 37 | Register, |
35 | 38 | TupleGet, |
36 | 39 | TupleSet, |
37 | 40 | Value, |
38 | 41 | ) |
39 | 42 | from mypyc.ir.rtypes import ( |
| 43 | + RInstance, |
40 | 44 | RTuple, |
41 | 45 | RType, |
42 | 46 | bool_rprimitive, |
|
48 | 52 | is_short_int_rprimitive, |
49 | 53 | is_str_rprimitive, |
50 | 54 | is_tuple_rprimitive, |
| 55 | + object_pointer_rprimitive, |
| 56 | + object_rprimitive, |
51 | 57 | pointer_rprimitive, |
52 | 58 | short_int_rprimitive, |
53 | 59 | ) |
|
62 | 68 | dict_next_value_op, |
63 | 69 | dict_value_iter_op, |
64 | 70 | ) |
65 | | -from mypyc.primitives.exc_ops import no_err_occurred_op |
| 71 | +from mypyc.primitives.exc_ops import no_err_occurred_op, propagate_if_error_op |
66 | 72 | from mypyc.primitives.generic_ops import aiter_op, anext_op, iter_op, next_op |
67 | 73 | from mypyc.primitives.list_ops import list_append_op, list_get_item_unsafe_op, new_list_set_item_op |
68 | 74 | from mypyc.primitives.misc_ops import stop_async_iteration_op |
@@ -511,7 +517,16 @@ def make_for_loop_generator( |
511 | 517 | # Default to a generic for loop. |
512 | 518 | if iterable_expr_reg is None: |
513 | 519 | iterable_expr_reg = builder.accept(expr) |
514 | | - for_obj = ForIterable(builder, index, body_block, loop_exit, line, nested) |
| 520 | + |
| 521 | + helper_method = "__mypyc_generator_helper__" |
| 522 | + it = iterable_expr_reg.type |
| 523 | + for_obj: ForNativeGenerator | ForIterable |
| 524 | + if isinstance(it, RInstance) and it.class_ir.has_method(helper_method): |
| 525 | + # Directly call generator object methods if iterating over a native generator. |
| 526 | + for_obj = ForNativeGenerator(builder, index, body_block, loop_exit, line, nested) |
| 527 | + else: |
| 528 | + # Generic implementation that works of arbitrary iterables. |
| 529 | + for_obj = ForIterable(builder, index, body_block, loop_exit, line, nested) |
515 | 530 | item_type = builder._analyze_iterable_item_type(expr) |
516 | 531 | item_rtype = builder.type_to_rtype(item_type) |
517 | 532 | for_obj.init(iterable_expr_reg, item_rtype) |
@@ -623,6 +638,56 @@ def gen_cleanup(self) -> None: |
623 | 638 | self.builder.call_c(no_err_occurred_op, [], self.line) |
624 | 639 |
|
625 | 640 |
|
| 641 | +class ForNativeGenerator(ForGenerator): |
| 642 | + """Generate IR for a for loop over a native generator.""" |
| 643 | + |
| 644 | + def need_cleanup(self) -> bool: |
| 645 | + # Create a new cleanup block for when the loop is finished. |
| 646 | + return True |
| 647 | + |
| 648 | + def init(self, expr_reg: Value, target_type: RType) -> None: |
| 649 | + # Define targets to contain the expression, along with the iterator that will be used |
| 650 | + # for the for-loop. If we are inside of a generator function, spill these into the |
| 651 | + # environment class. |
| 652 | + builder = self.builder |
| 653 | + self.iter_target = builder.maybe_spill(expr_reg) |
| 654 | + self.target_type = target_type |
| 655 | + |
| 656 | + def gen_condition(self) -> None: |
| 657 | + builder = self.builder |
| 658 | + line = self.line |
| 659 | + helper_method = "__mypyc_generator_helper__" |
| 660 | + self.return_value = Register(object_rprimitive) |
| 661 | + err = builder.add(LoadErrorValue(object_rprimitive, undefines=True)) |
| 662 | + builder.assign(self.return_value, err, line) |
| 663 | + ptr = builder.add(LoadAddress(object_pointer_rprimitive, self.return_value)) |
| 664 | + nn = builder.none_object() |
| 665 | + helper_call =( |
| 666 | + MethodCall(builder.read(self.iter_target), helper_method, [nn, nn, nn, nn, ptr], line) |
| 667 | + ) |
| 668 | + # We provide custom handling for error values. |
| 669 | + helper_call.error_kind = ERR_NEVER |
| 670 | + self.next_reg = builder.add(helper_call) |
| 671 | + builder.add(Branch(self.next_reg, self.loop_exit, self.body_block, Branch.IS_ERROR)) |
| 672 | + |
| 673 | + def begin_body(self) -> None: |
| 674 | + # Assign the value obtained from __next__ to the |
| 675 | + # lvalue so that it can be referenced by code in the body of the loop. |
| 676 | + builder = self.builder |
| 677 | + line = self.line |
| 678 | + # We unbox here so that iterating with tuple unpacking generates a tuple based |
| 679 | + # unpack instead of an iterator based one. |
| 680 | + next_reg = builder.coerce(self.next_reg, self.target_type, line) |
| 681 | + builder.assign(builder.get_assignment_target(self.index), next_reg, line) |
| 682 | + |
| 683 | + def gen_step(self) -> None: |
| 684 | + # Nothing to do here, since we get the next item as part of gen_condition(). |
| 685 | + pass |
| 686 | + |
| 687 | + def gen_cleanup(self) -> None: |
| 688 | + self.builder.primitive_op(propagate_if_error_op, [self.return_value], self.line) |
| 689 | + |
| 690 | + |
626 | 691 | class ForAsyncIterable(ForGenerator): |
627 | 692 | """Generate IR for an async for loop.""" |
628 | 693 |
|
|
0 commit comments