Repository navigation
break label out of a switch in an async generator throws TypeError reading 'done' #9199
Description
Activity
Taking this. I'll reproduce against current main (which already carries #9189), work out why the labeled break leaves the async-generator state machine without a well-formed iterator result, add a gap regression, and open a PR.
Re-measured against current
main(28c292517, which carries #9189). Two things changed since this was filed, and the second is why the issue should stay open.This issue's own repro now passes. The one-level shape —
break loopfrom aswitchinside a single labeledfor…of— printsy1,d3, matching node. #9189 covered it after all, despite the note here that it was out of scope.The defect it describes is still live, one level deeper, with the identical error. The switch turns out to be a red herring; the trigger is a labeled
break/continuethat targets an outer loop from inside a nested loop, after ayield. Three isolated programs, each on its own:// p6 — break an outer label from the inner loop async function* p6() { O: for (const x of [1,2]) { I: for (const y of [0,1]) { yield "p6"+x+y; break O; } } } // node: p610 perry: TypeError: Cannot read properties of undefined (reading 'done') // p5 — continue an outer label from the inner loop async function* p5() { O: for (const x of [1,2]) { I: for (const y of [0,1]) { yield "p5"+x+y; continue O; } } } // node: p510,p520 perry: NO OUTPUT AT ALL — the `for await` never yields and never settles // pD — this issue's shape with the switch deleted async function* d() { outer: for (const x of [1,2]) { inner: for (const y of [0,1]) { if (x+y === 3) break outer; if (x+y === 1) { yield "D"+x+y; continue inner; } } } } // node: D10 perry: TypeError: Cannot read properties of undefined (reading 'done')
p6's error is character-for-character the one reported here, so this is the same defect, not a new one.What still works, which brackets it tightly:
shape result labeled continueon the same loop, after a yieldOK labeled continueof the inner label from the inner loopOK unlabeled continueafter a yieldOK labeled continueover awhileOK labeled break/continue in a sync generator, same shape OK break outerfrom aswitchin the inner loop, when nothing yields in the inner loopOK break/continueof an OUTER label from a nested loop, after ayieldbroken So the surviving hole is specifically the cross-loop target: one linearized loop's label resolved from inside another linearized loop, where the inner loop has a suspension point.
continueandbreakfail differently (silent drop vs. malformed iterator result), which suggests the target resumption state is wrong rather than absent.Also worth noting for whoever picks this up: the sibling
async functionhang mentioned at the bottom of this issue does not reproduce —asyncfn-break-labelreturnsf1,f3, matching node.I'm working on this now; probes are on the build host under
/root/cc9575/ts/iso_p5.ts,iso_p6.ts,iso_pD.ts.- added a commit that references this issue
on Sep 4, 2026
A
break labeltargeting a labeledfor…offrom inside aswitch, in an async generator, throws instead of breaking.y1,d3Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'done')The
doneread failing points at the async-iterator protocol: the labeled break appears to leave the generator state machine without producing a well-formed iterator result, sofor awaitreads.doneoffundefined. The plainbreakincase 1is fine — it is the labeled one that targets the enclosingfor…ofthat fails.Confirmed pre-existing on
mainby revertingcrates/perry-transform/src/generator/linearize.rstomainand rebuilding: identical error, exit 1 both ways. Found while auditing #9189; that PR fixes the labeled-switchcase (#9186) and this async-generator shape is not in its scope.Sibling: the
async functionform with a labeledfor…ofhangs rather than throwing, filed separately.