diff --git a/packages/compiler/src/frontend/lowering/builtins/promises.ts b/packages/compiler/src/frontend/lowering/builtins/promises.ts index c30590619..3aecf87e3 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promises.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promises.ts @@ -72,7 +72,28 @@ function lowerTuplePromiseAll( const voidPromise: IrType & { kind: "promise" } = { kind: "promise", inner: VOID }; const entriesT = arrayOf(voidPromise); const dynPromise: IrType = { kind: "promise", inner: DYN }; - const entries = elems.map((e): IrExpr => { + // A checked-dynamic entry's PromiseResolve can run a `then` getter, which + // Node runs after the whole array literal evaluated. With such an entry, + // every entry evaluates into a hidden local first. + const staged: IrStmt[] = []; + const values = !elems.some((e) => e.type.kind === "dyn") + ? elems + : elems.map((e): IrExpr => { + if (e.kind === "unitLit") return e; + if (isUnitType(e.type)) { + staged.push({ kind: "exprStmt", expr: e, loc }); + return { + kind: "unitLit", + unit: e.type.kind === "nullT" ? "null" : "undefined", + type: e.type, + loc, + }; + } + const local = lowerer.declareHiddenLocal("%allValue", e.type); + staged.push({ kind: "varDecl", localId: local.id, init: e, loc }); + return varRef(local.id, e.type, loc); + }); + const entries = values.map((e): IrExpr => { let promise: IrExpr; if (e.type.kind === "promise") promise = e; else if (e.type.kind === "dyn") @@ -164,7 +185,7 @@ function lowerTuplePromiseAll( loc, }); } - return { + const all: IrExpr = { kind: "libCall", fn: "promise.allTuple", args: [ @@ -174,6 +195,9 @@ function lowerTuplePromiseAll( type: resultT, loc, }; + return staged.length === 0 + ? all + : { kind: "seqExpr", stmts: staged, result: all, type: resultT, loc }; } /** `Promise.race([...])` on THE Promise global: the entries lower diff --git a/tests/corpus/7035-promise-all-tuple-turns.ts b/tests/corpus/7035-promise-all-tuple-turns.ts index 6989df9b9..1f8a8803d 100644 --- a/tests/corpus/7035-promise-all-tuple-turns.ts +++ b/tests/corpus/7035-promise-all-tuple-turns.ts @@ -26,6 +26,16 @@ async function text(): Promise { async function both(): Promise<[number, string]> { return Promise.all([num(), text()]); } +const thenable: any = { + get then() { + log.push("then getter"); + return (resolve: (v: number) => void) => resolve(1); + }, +}; +function evaluated(): Promise { + log.push("second entry evaluated"); + return Promise.resolve("s"); +} interface Item { id: number; } @@ -49,6 +59,9 @@ async function main(): Promise { log.push(`returned ${h}${i}`); const mixed = await Promise.all([later("x", 3, "x"), 5, later("y", 1, true), undefined]); log.push(`mixed ${mixed[0]} ${mixed[1]} ${mixed[2]} ${mixed[3]}`); + // A dynamic entry's `then` getter runs after the whole array evaluated. + const [fromThenable, second] = await Promise.all([thenable, evaluated()]); + log.push(`thenable ${fromThenable} ${second}`); const raw: any = JSON.parse('{"value":41}'); const [dynamic, counted] = await Promise.all([raw.value, later("z", 2, 2)]); log.push(`dynamic ${dynamic + counted}`);