From ce6be86afbf7f95ff6a4446882ca510b4c91ce77 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 17:37:15 -0700 Subject: [PATCH] Await each value of for await over an untyped array A JavaScript array literal that mixes promises and plain values lowers as a checked-dynamic value, and for await over it took the synchronous iteration path, binding the promises themselves. It now takes the async mode of the checked-dynamic loop, which awaits each value. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-stmts.ts | 6 ++++-- .../6600-npm-await-mixed-array/main.ts.json | 6 ++++++ .../corpus/6600-npm-await-mixed-array/main.ts | 6 ++++++ .../node_modules/mixed-awaits/index.d.ts | 1 + .../node_modules/mixed-awaits/index.js | 19 +++++++++++++++++++ .../node_modules/mixed-awaits/package.json | 1 + 6 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6600-npm-await-mixed-array/main.ts.json create mode 100644 tests/corpus/6600-npm-await-mixed-array/main.ts create mode 100644 tests/corpus/6600-npm-await-mixed-array/node_modules/mixed-awaits/index.d.ts create mode 100644 tests/corpus/6600-npm-await-mixed-array/node_modules/mixed-awaits/index.js create mode 100644 tests/corpus/6600-npm-await-mixed-array/node_modules/mixed-awaits/package.json diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 22c5d24b5..9d58ed179 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -9480,9 +9480,11 @@ export function lowerForOf(lowerer: Lowerer, stmt: ts.ForOfStatement): IrStmt { // A CHECKED-DYNAMIC iterable (`unknown[]`, the collapsed // `(string | object)[]`, JSON-parsed values): a checked IteratorRecord - // binds each yielded dyn value. + // binds each yielded dyn value. Under `for await` (a JavaScript array + // literal mixing promises and plain values) the async-from-sync rules + // await each value. if (iterable.type.kind === "dyn") { - return lowerForOfDyn(lowerer, stmt, iterable, labels); + return lowerForOfDyn(lowerer, stmt, iterable, labels, stmt.awaitModifier !== undefined); } if (tupleSource === null) { lowerer.badType(stmt.expression, lowerer.typeOf(stmt.expression)); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6600-npm-await-mixed-array/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6600-npm-await-mixed-array/main.ts.json new file mode 100644 index 000000000..a31991cf9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6600-npm-await-mixed-array/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6600-npm-await-mixed-array/main.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6600-npm-await-mixed-array/main.ts b/tests/corpus/6600-npm-await-mixed-array/main.ts new file mode 100644 index 000000000..0d0b3e14f --- /dev/null +++ b/tests/corpus/6600-npm-await-mixed-array/main.ts @@ -0,0 +1,6 @@ +// for await over JavaScript array literals mixing promises and plain values +// (in a dependency): every value is awaited before the body runs, a break +// ends the loop, and a rejected element rejects the loop at that step. +import { run } from 'mixed-awaits'; + +for (const line of await run()) console.log(line); diff --git a/tests/corpus/6600-npm-await-mixed-array/node_modules/mixed-awaits/index.d.ts b/tests/corpus/6600-npm-await-mixed-array/node_modules/mixed-awaits/index.d.ts new file mode 100644 index 000000000..94e93439d --- /dev/null +++ b/tests/corpus/6600-npm-await-mixed-array/node_modules/mixed-awaits/index.d.ts @@ -0,0 +1 @@ +export declare function run(): Promise; diff --git a/tests/corpus/6600-npm-await-mixed-array/node_modules/mixed-awaits/index.js b/tests/corpus/6600-npm-await-mixed-array/node_modules/mixed-awaits/index.js new file mode 100644 index 000000000..90e9d8fbd --- /dev/null +++ b/tests/corpus/6600-npm-await-mixed-array/node_modules/mixed-awaits/index.js @@ -0,0 +1,19 @@ +// for await over JavaScript array literals that mix promises and plain +// values: each value is awaited, as the async-from-sync iterator does. +'use strict'; +async function run() { + var out = []; + for await (var p of [Promise.resolve('p1'), 'p2', Promise.resolve(3)]) out.push(typeof p + ':' + p); + for await (let q of [1, Promise.resolve({ n: 2 }), null]) out.push(JSON.stringify(q)); + for await (const r of ['a', Promise.resolve('b'), 'c']) { + if (r === 'b') break; + out.push('r:' + r); + } + try { + for await (const s of ['ok', Promise.reject(new Error('rejected element')), 'never']) out.push('s:' + s); + } catch (error) { + out.push(error.message); + } + return out; +} +module.exports = { run }; diff --git a/tests/corpus/6600-npm-await-mixed-array/node_modules/mixed-awaits/package.json b/tests/corpus/6600-npm-await-mixed-array/node_modules/mixed-awaits/package.json new file mode 100644 index 000000000..b408bc093 --- /dev/null +++ b/tests/corpus/6600-npm-await-mixed-array/node_modules/mixed-awaits/package.json @@ -0,0 +1 @@ +{ "name": "mixed-awaits", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" }