Repeatedly type check a loop body until the frame doesn't change.
(
self,
body: Statement,
else_body: Statement | None = None,
*,
exit_condition: Expression | None = None,
on_enter_body: Callable[[], None] | None = None,
)
| 767 | report_internal_error(err, self.errors.file, stmt.line, self.errors, self.options) |
| 768 | |
| 769 | def accept_loop( |
| 770 | self, |
| 771 | body: Statement, |
| 772 | else_body: Statement | None = None, |
| 773 | *, |
| 774 | exit_condition: Expression | None = None, |
| 775 | on_enter_body: Callable[[], None] | None = None, |
| 776 | ) -> None: |
| 777 | """Repeatedly type check a loop body until the frame doesn't change.""" |
| 778 | |
| 779 | # The outer frame accumulates the results of all iterations: |
| 780 | with self.binder.frame_context(can_skip=False, conditional_frame=True): |
| 781 | # Check for potential decreases in the number of partial types so as not to stop the |
| 782 | # iteration too early: |
| 783 | partials_old = sum(len(pts.map) for pts in self.partial_types) |
| 784 | # Check if assignment widened the inferred type of a variable; in this case we |
| 785 | # need to iterate again (we only do one extra iteration, since this could go |
| 786 | # on without bound otherwise) |
| 787 | widened_old = len(self.widened_vars) |
| 788 | |
| 789 | iter_errors = IterationDependentErrors() |
| 790 | iter = 1 |
| 791 | while True: |
| 792 | with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1): |
| 793 | if on_enter_body is not None: |
| 794 | on_enter_body() |
| 795 | |
| 796 | with IterationErrorWatcher(self.msg.errors, iter_errors): |
| 797 | self.accept(body) |
| 798 | |
| 799 | partials_new = sum(len(pts.map) for pts in self.partial_types) |
| 800 | widened_new = len(self.widened_vars) |
| 801 | # Perform multiple iterations if something changed that might affect |
| 802 | # inferred types. Also limit the number of iterations. The limits are |
| 803 | # somewhat arbitrary, but they were chosen to 1) avoid slowdown from |
| 804 | # multiple iterations in common cases and 2) support common, valid use |
| 805 | # cases. Limits are needed since otherwise we could infer infinitely |
| 806 | # complex types. |
| 807 | if ( |
| 808 | (partials_new == partials_old) |
| 809 | and (not self.binder.last_pop_changed or iter > 3) |
| 810 | and (widened_new == widened_old or iter > 1) |
| 811 | ): |
| 812 | break |
| 813 | partials_old = partials_new |
| 814 | widened_old = widened_new |
| 815 | iter += 1 |
| 816 | if iter == 20: |
| 817 | raise RuntimeError("Too many iterations when checking a loop") |
| 818 | |
| 819 | self.msg.iteration_dependent_errors(iter_errors) |
| 820 | |
| 821 | # If exit_condition is set, assume it must be False on exit from the loop: |
| 822 | if exit_condition: |
| 823 | _, else_map = self.find_isinstance_check(exit_condition) |
| 824 | self.push_type_map(else_map) |
| 825 | |
| 826 | # Check the else body: |
no test coverage detected