truncateLoopStacks method

void truncateLoopStacks(
  1. LoopStackDepths depths
)

Restores every loop stack to depths, forgetting the loops that are removed — so a loop entered again later starts from scratch instead of resuming a stale iterator or list.

Implementation

void truncateLoopStacks(LoopStackDepths depths) {
  for (final node in loopNodeStack.skip(depths.nodes)) {
    forInIteratorMap.remove(node);
    loopEntryDepths.remove(node);
  }
  for (final node in awaitForNodeStack.skip(depths.awaitFor)) {
    loopEntryDepths.remove(node);
  }
  // SCE16: an await-for being left owns a live subscription. Cancelling it is
  // what stops the producer — an `async*` generator resumes at its `yield`
  // and runs its `finally` blocks, and an infinite stream stops arriving.
  // Leaving it uncancelled is how a `break` used to mean nothing to the
  // stream.
  //
  // NOT awaited, because every caller of this is a synchronous exit path in
  // the state machine (break, continue, loop end, an exception unwinding).
  // The cancel still happens promptly — it is the ORDERING against code after
  // the loop that is not guaranteed, which is why a test that observes a
  // generator's `finally` has to await a turn first.
  for (final iterator in awaitForIteratorStack.skip(depths.awaitFor)) {
    unawaited(iterator.cancel().catchError((Object _) {}));
  }
  _shrink(loopNodeStack, depths.nodes);
  _shrink(loopEnvironmentStack, depths.environments);
  _shrink(loopInitializedStack, depths.initialized);
  _shrink(awaitForNodeStack, depths.awaitFor);
  _shrink(awaitForIteratorStack, depths.awaitFor);
  _shrink(awaitForHasElementStack, depths.awaitFor);
  if (awaitForNodeStack.isEmpty) {
    currentAwaitForList = null;
    currentAwaitForIndex = null;
  }
}