diff --git a/Compilation/AsyncGeneratorMoveNextEmitter.Statements.TryCatch.cs b/Compilation/AsyncGeneratorMoveNextEmitter.Statements.TryCatch.cs
index 1a998277..a2fa90d8 100644
--- a/Compilation/AsyncGeneratorMoveNextEmitter.Statements.TryCatch.cs
+++ b/Compilation/AsyncGeneratorMoveNextEmitter.Statements.TryCatch.cs
@@ -964,71 +964,9 @@ private static bool ContainsSuspensionInExpr(Expr expr)
}
}
- ///
- /// Detects return/break/continue that would transfer control out of the surrounding try
- /// region. Over-approximates conservatively (labeled break/continue are always treated as
- /// escaping): a false positive only costs a statement some mini-segment exception coverage,
- /// whereas a false negative would emit a `br`/`ret` inside a protected region (illegal IL).
- /// Nested function/arrow bodies are not traversed (their returns are their own).
- ///
- private static bool ContainsEscapingExit(Stmt stmt, bool insideLoop, bool insideSwitch)
- {
- switch (stmt)
- {
- case Stmt.Return:
- return true;
- case Stmt.Break b:
- return b.Label != null || !(insideLoop || insideSwitch);
- case Stmt.Continue c:
- return c.Label != null || !insideLoop;
- case Stmt.If i:
- return ContainsEscapingExit(i.ThenBranch, insideLoop, insideSwitch)
- || (i.ElseBranch != null && ContainsEscapingExit(i.ElseBranch, insideLoop, insideSwitch));
- case Stmt.Block b:
- if (b.Statements == null) return false;
- foreach (var s in b.Statements)
- if (ContainsEscapingExit(s, insideLoop, insideSwitch)) return true;
- return false;
- case Stmt.Sequence seq:
- foreach (var s in seq.Statements)
- if (ContainsEscapingExit(s, insideLoop, insideSwitch)) return true;
- return false;
- case Stmt.While w:
- return ContainsEscapingExit(w.Body, insideLoop: true, insideSwitch);
- case Stmt.DoWhile dw:
- return ContainsEscapingExit(dw.Body, insideLoop: true, insideSwitch);
- case Stmt.For f:
- return ContainsEscapingExit(f.Body, insideLoop: true, insideSwitch);
- case Stmt.ForOf fo:
- return ContainsEscapingExit(fo.Body, insideLoop: true, insideSwitch);
- case Stmt.ForIn fi:
- return ContainsEscapingExit(fi.Body, insideLoop: true, insideSwitch);
- case Stmt.Switch s:
- foreach (var c in s.Cases)
- foreach (var cs in c.Body)
- if (ContainsEscapingExit(cs, insideLoop, insideSwitch: true)) return true;
- if (s.DefaultBody != null)
- foreach (var ds in s.DefaultBody)
- if (ContainsEscapingExit(ds, insideLoop, insideSwitch: true)) return true;
- return false;
- case Stmt.LabeledStatement ls:
- return ContainsEscapingExit(ls.Statement, insideLoop, insideSwitch);
- case Stmt.TryCatch t:
- if (ContainsEscapingExit2(t.TryBlock, insideLoop, insideSwitch)) return true;
- if (t.CatchBlock != null && ContainsEscapingExit2(t.CatchBlock, insideLoop, insideSwitch)) return true;
- if (t.FinallyBlock != null && ContainsEscapingExit2(t.FinallyBlock, insideLoop, insideSwitch)) return true;
- return false;
- default:
- return false;
- }
- }
-
- private static bool ContainsEscapingExit2(List statements, bool insideLoop, bool insideSwitch)
- {
- foreach (var s in statements)
- if (ContainsEscapingExit(s, insideLoop, insideSwitch)) return true;
- return false;
- }
+ // ContainsEscapingExit / ContainsEscapingExit2 are shared across the suspension-aware emitters and
+ // live in StatementEmitterBase (the generator, async-generator, and async-function emitters all
+ // segment a flag-based try body around non-local exits using the same conservative analysis).
#endregion
}
diff --git a/Compilation/AsyncMoveNextEmitter.Statements.ControlFlow.cs b/Compilation/AsyncMoveNextEmitter.Statements.ControlFlow.cs
index fbe2affa..8646bba5 100644
--- a/Compilation/AsyncMoveNextEmitter.Statements.ControlFlow.cs
+++ b/Compilation/AsyncMoveNextEmitter.Statements.ControlFlow.cs
@@ -39,5 +39,22 @@ protected override void EmitReturn(Stmt.Return r)
_il.Emit(OpCodes.Leave, _setResultLabel);
}
+ ///
+ /// Branch out to (a loop's break/continue label). Inside a real IL
+ /// exception block a br out is illegal IL (BranchOutOfTry), so use Leave — which exits
+ /// the block legally and runs its (no-await) finally. ExceptionBlockDepth counts only real
+ /// blocks opened by , not the flag-based path's mini try/catch
+ /// segments (EmitSegmentInTry), so an escaping break/continue in a try-with-awaits is pulled out as a
+ /// segment-breaker (emitted at the top level, depth 0 → Br) while a break targeting a loop
+ /// nested inside a segment stays a legal in-segment Br. Mirrors the generator emitters (#727).
+ ///
+ protected override void EmitBranchToLabel(Label target)
+ {
+ if (_ctx!.ExceptionBlockDepth > 0)
+ _il.Emit(OpCodes.Leave, target);
+ else
+ _il.Emit(OpCodes.Br, target);
+ }
+
// EmitIf: inherited from StatementEmitterBase (identical logic)
}
diff --git a/Compilation/AsyncMoveNextEmitter.Statements.LabeledLoops.cs b/Compilation/AsyncMoveNextEmitter.Statements.LabeledLoops.cs
index 54de9271..52eeb405 100644
--- a/Compilation/AsyncMoveNextEmitter.Statements.LabeledLoops.cs
+++ b/Compilation/AsyncMoveNextEmitter.Statements.LabeledLoops.cs
@@ -113,6 +113,17 @@ private void EmitLabeledWhile(IReadOnlyList labelNames, Stmt.While w, La
private void EmitLabeledForOf(IReadOnlyList labelNames, Stmt.ForOf f, Label outerBreakLabel)
{
+ if (f.IsAsync)
+ {
+ // for await: route to the suspending async-iterator lowering, carrying the chain's labels so
+ // `break`/`continue