diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 0e7de32f0..22c5d24b5 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -10807,8 +10807,8 @@ function lowerForOfDyn( } /** One step of `for await` over a checked-dynamic iterator: await next()'s - * result, read done before value, and await the value of a synchronous - * iterator, as the async-from-sync iterator does. */ + * result, read done before value, and await each value of a synchronous + * iterator, including its final one, as the async-from-sync iterator does. */ function asyncIteratorValue( lowerer: Lowerer, iterator: IrExpr, @@ -10866,14 +10866,29 @@ function asyncIteratorValue( loc, }, ], + // A synchronous iterator's final value is still read and awaited, so a + // throwing getter or a rejected promise there rejects the loop. result: { kind: "ternary", - cond: varRef(done.id, BOOL, loc), - then: dynUndefinedExpr(loc), + cond: sync, + then: { + kind: "ternary", + cond: varRef(done.id, BOOL, loc), + then: { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: awaitDyn(get("value")), loc }], + result: dynUndefinedExpr(loc), + type: DYN, + loc, + }, + else_: awaitDyn(get("value")), + type: DYN, + loc, + }, else_: { kind: "ternary", - cond: sync, - then: awaitDyn(get("value")), + cond: varRef(done.id, BOOL, loc), + then: dynUndefinedExpr(loc), else_: get("value"), type: DYN, loc, diff --git a/tests/corpus/6715-dyn-for-await-protocol.ts b/tests/corpus/6715-dyn-for-await-protocol.ts index 63680d0cd..bbf037372 100644 --- a/tests/corpus/6715-dyn-for-await-protocol.ts +++ b/tests/corpus/6715-dyn-for-await-protocol.ts @@ -1,6 +1,25 @@ // for await and async yield* over values held as any: a Symbol.asyncIterator // method that returns a compiled generator, a synchronous delegate whose -// final value is a promise, and return() results that are not objects. +// final value is a promise, a synchronous iterator whose final value throws +// or rejects, and return() results that are not objects. +const settle: any = (value: unknown, fails: boolean) => + new Promise((resolve, reject) => (fails ? reject(new Error(String(value))) : resolve(value))); +function finishing(last: () => any): any { + return { + [Symbol.iterator]() { + let step = 0; + return { next: () => (step++ < 1 ? { value: 1, done: false } : last()) }; + }, + }; +} +async function loop(label: string, source: any): Promise { + try { + for await (const value of source) console.log(label, "value", value); + console.log(label, "completed"); + } catch (error) { + console.log(label, "caught", (error as Error).message); + } +} async function main(): Promise { const generated: any = { async *[Symbol.asyncIterator]() { @@ -56,5 +75,19 @@ async function main(): Promise { } catch (error) { console.log("return", (error as Error).name, (error as Error).message); } + + await loop("getter", finishing(() => ({ done: true, get value(): number { throw new Error("value getter"); } }))); + await loop("rejected", finishing(() => ({ value: settle("rejected final", true), done: true }))); + await loop("resolved", finishing(() => ({ value: settle(9, false), done: true }))); + await loop("rejected step", finishing(() => ({ value: settle("rejected step", true), done: false }))); + async function* finalRejects(): AsyncGenerator { + const result = yield* finishing(() => ({ value: settle("delegate final", true), done: true })); + console.log("delegate completed", result); + } + try { + for await (const value of finalRejects()) console.log("delegate value", value); + } catch (error) { + console.log("delegate caught", (error as Error).message); + } } main(); diff --git a/tests/harness/corpus-costs/6715-dyn-for-await-protocol.ts.json b/tests/harness/corpus-costs/6715-dyn-for-await-protocol.ts.json index e8f062569..dd46280d7 100644 --- a/tests/harness/corpus-costs/6715-dyn-for-await-protocol.ts.json +++ b/tests/harness/corpus-costs/6715-dyn-for-await-protocol.ts.json @@ -1 +1 @@ -{ "plain": 3200, "san": 3800 } +{ "plain": 3200, "san": 4100 }