Summary
In compiled mode, a for await (… of asyncGen()) loop placed inside an async arrow function (async () => { … }) crashes at runtime with:
System.InvalidCastException: Unable to cast object of type '<g>d__0' to type 'System.Collections.IEnumerable'.
at <>c__AsyncArrow_0.MoveNext()
The async-arrow MoveNext emitter appears to lower for await as a synchronous for-of (it casts the async-generator state machine to System.Collections.IEnumerable) instead of async iteration. The same loop works when it lives in an async function declaration, and works in both forms under the interpreter.
Repro
async function* g() { yield 1; yield 2; }
(async () => {
for await (const x of g()) console.log(x); // compiled: InvalidCastException
})();
Contrast — these all work:
// async FUNCTION wrapper instead of arrow — works compiled + interpreted
async function* g() { yield 1; yield 2; }
async function run() { for await (const x of g()) console.log(x); }
run();
The arrow form works under the interpreter (dotnet run script.ts); it only fails when compiled (--compile).
Impact / how found
Found while implementing #635 (parser support for async function () {} expressions). With #635, an async generator function expression const ag = async function* () { … } now parses; GeneratorArrowLifter lifts it to a top-level async function* declaration, and any compiled consumer that uses for await inside an async arrow hits this bug. The bug itself is independent of #635 — it reproduces with a hand-written async function* declaration and no expression syntax (above).
Likely area
The async-arrow state-machine MoveNext emitter's handling of Stmt.ForOf with IsAsync = true — it likely falls through to the synchronous for-of path (emitting an IEnumerable cast / GetEnumerator) rather than the async-iterator path used by the async-function-declaration emitter. Compare the for await emission in the async function MoveNext emitter (which is correct) with the async arrow one.
Summary
In compiled mode, a
for await (… of asyncGen())loop placed inside an async arrow function (async () => { … }) crashes at runtime with:The async-arrow
MoveNextemitter appears to lowerfor awaitas a synchronousfor-of(it casts the async-generator state machine toSystem.Collections.IEnumerable) instead of async iteration. The same loop works when it lives in an async function declaration, and works in both forms under the interpreter.Repro
Contrast — these all work:
The arrow form works under the interpreter (
dotnet run script.ts); it only fails when compiled (--compile).Impact / how found
Found while implementing #635 (parser support for
async function () {}expressions). With #635, an async generator function expressionconst ag = async function* () { … }now parses;GeneratorArrowLifterlifts it to a top-levelasync function*declaration, and any compiled consumer that usesfor awaitinside an async arrow hits this bug. The bug itself is independent of #635 — it reproduces with a hand-writtenasync function*declaration and no expression syntax (above).Likely area
The async-arrow state-machine
MoveNextemitter's handling ofStmt.ForOfwithIsAsync = true— it likely falls through to the synchronous for-of path (emitting anIEnumerablecast /GetEnumerator) rather than the async-iterator path used by the async-function-declaration emitter. Compare thefor awaitemission in the async function MoveNext emitter (which is correct) with the async arrow one.