From 279d42e5624436b28d70178e95e4098abbab6c6a Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 27 Oct 2026 21:11:41 -0700 Subject: [PATCH 1/6] Lower a discarded `undefined` catch handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Promise.catch(() => undefined)` tripped the IR validator's bare-unitLit rule: the handler body lowered as a bare expression, and `undefined` has no standalone IR value. The handler's value is discarded, so lower the body as a statement — the same rule a discarded `undefined;` statement already follows. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-calls.ts | 5 ++- tests/corpus/promise-void-catch-undefined.ts | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/corpus/promise-void-catch-undefined.ts diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 6229917a2..45d470c60 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -6908,7 +6908,10 @@ export function lowerPromiseMethodCall( if (ts.isBlock(hb)) { catchBody = lowerer.lowerStmts(hb.statements.nodes); } else if (R.kind === "void") { - catchBody = [{ kind: "exprStmt", expr: lowerer.lowerExpr(hb), loc: locOf(hb) }]; + // The handler's value is discarded, so the body lowers as a + // statement: `() => undefined` is a pure no-op (a bare unit + // literal has no IR value), not an expression to evaluate. + catchBody = [lowerer.lowerExprStatement(hb)]; } else { // Bare-expression handler: `(e) => v` (promise results flatten // through the async-return path, like any `return v`). diff --git a/tests/corpus/promise-void-catch-undefined.ts b/tests/corpus/promise-void-catch-undefined.ts new file mode 100644 index 000000000..ee7285d8a --- /dev/null +++ b/tests/corpus/promise-void-catch-undefined.ts @@ -0,0 +1,31 @@ +// `Promise.catch(() => undefined)` — the handler's value is +// discarded and the recovered promise settles as void. The handler runs +// exactly once, and the chain continues on the next turn whether the +// receiver fulfilled or rejected. + +const ok: Promise = Promise.resolve(); +const bad: Promise = Promise.reject(new Error("boom")); + +ok.catch(() => undefined).then(() => console.log("fulfilled recovered")); + +bad + .catch(() => { + console.log("rejected recovered"); + }) + .then(() => console.log("rejected continued")); + +let runs = 0; + +function recover(): Promise { + runs++; + return runs % 2 === 1 ? Promise.reject(new Error("odd")) : Promise.resolve(); +} + +async function run(): Promise { + await recover().catch(() => undefined); + console.log("awaited recover", runs); + await recover().catch(() => undefined); + console.log("awaited recover", runs); +} + +void run().then(() => console.log("done")); From bf285950351105acf9463e99635fbc5a4d3ef4b9 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 27 Oct 2026 21:12:28 -0700 Subject: [PATCH 2/6] Lower promise methods called through an optional chain `p?.catch(h)`, `p?.then(h)` and a guarded chain tail (`f()?.then(h)`) were refused: the promise-method lowering rejected every `questionDotToken` instead of asking whether the optional chain was currently being lowered, so the guarded body never reached it. It uses `chainBlocked` now, like every other lowering that rides the chain. The call that ENDS a chain checker-types as the chain's undefined-armed union (`Promise | undefined`) while this lowering builds the promise arm, which `finishOptionalChain` wraps back into the union. The handler's result type takes that arm, so the handler is typed against the promise it actually handles. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-calls.ts | 24 +++++-- .../src/frontend/lowering/maybe-promise.ts | 12 ++++ tests/corpus/promise-catch-optional-chain.ts | 64 +++++++++++++++++++ 3 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 tests/corpus/promise-catch-optional-chain.ts diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 45d470c60..34587b078 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -279,7 +279,11 @@ import { isThisParameter, type ParamShape } from "./call-signatures.js"; import { dynamicOverloadChoice } from "./overload-choice.js"; import { lowerStringIteratorValue } from "./iterator-adapters.js"; import { checkedArmExtraction } from "./coercions/unions.js"; -import { maybePromiseReturnStmt, promiseArmedUnionOf } from "./maybe-promise.js"; +import { + maybePromiseReturnStmt, + promiseArmedUnionOf, + promiseOrUnitUnionOf, +} from "./maybe-promise.js"; import { requireObjLitGenericReceiver, objLitGenericFnNodeOf, @@ -5998,9 +6002,19 @@ export function lowerPromiseMethodCall( call: ts.CallExpression, access: ts.PropertyAccessExpression, ): IrExpr | null { - if (call.questionDotToken || access.questionDotToken) return null; + if (lowerer.chainBlocked(access, call)) return null; const member = access.name.text; if (member !== "then" && member !== "catch" && member !== "finally") return null; + // A call that ENDS an optional chain (`refreshing?.catch(h)`, + // `client?.close().catch(h)`) checker-types as the chain's + // undefined-armed union while the body this lowering builds is the + // promise arm — `finishOptionalChain` wraps it back into the union. + // Take the arm so the handler typing sees the promise it handles. + const callResultType = (): IrType | undefined => { + const t = lowerer.mapTypeOf(lowerer.typeOf(call)); + if (!t || t.kind === "promise") return t ?? undefined; + return promiseOrUnitUnionOf(lowerer, t)?.promise ?? t; + }; let recvT = lowerer.mapTypeOf(lowerer.typeOf(access.expression)) ?? dynFallbackType(lowerer, access.expression, lowerer.typeOf(access.expression)); @@ -6117,7 +6131,7 @@ export function lowerPromiseMethodCall( const rejectedNode = call.arguments.nodes[1]!; const fulfilledAbsent = isAbsentHandler(fulfilledNode); const rejectedAbsent = isAbsentHandler(rejectedNode); - const resultT = lowerer.mapTypeOf(lowerer.typeOf(call)); + const resultT = callResultType(); // An untyped call in JavaScript whose receiver arrived as a typed // promise (a function instantiated with one): Promise.prototype.then // over the promise as a checked-dynamic value, which shares its @@ -6516,7 +6530,7 @@ export function lowerPromiseMethodCall( `then handlers whose parameter is not the settled value's type (expected '${lowerer.fmt(inner)}', got '${lowerer.fmt(param)}')`, ); } - let resultT = lowerer.mapTypeOf(lowerer.typeOf(call)); + let resultT = callResultType(); // JavaScript whose declared result type has no mapping (zod's // `Promise.all(items).then((r) => ParseStatus.mergeArray(status, r))`, // typed by a .d.ts union): the handler's value settles the promise as @@ -6864,7 +6878,7 @@ export function lowerPromiseMethodCall( "value — take `(e)` or `(e: unknown)` and narrow with instanceof)", ); } - const resultT = lowerer.mapTypeOf(lowerer.typeOf(call)); + const resultT = callResultType(); if (resultT?.kind !== "promise") { lowerer.noLowering( "catch with this handler's result type", diff --git a/packages/compiler/src/frontend/lowering/maybe-promise.ts b/packages/compiler/src/frontend/lowering/maybe-promise.ts index 8323c2e91..8cc7d7e35 100644 --- a/packages/compiler/src/frontend/lowering/maybe-promise.ts +++ b/packages/compiler/src/frontend/lowering/maybe-promise.ts @@ -23,6 +23,18 @@ export function maybePromiseUnionOf(lowerer: Lowerer, t: IrType): MaybePromiseUn return armed; } +/** The promise arm of a promise-or-unit union (`Promise | void`, + * `Promise | undefined`): exactly one promise arm and only nullish + * arms beside it, so no other arm carries a value the promise path would + * have to reconcile. A guard that proves the value present (`?.`, an + * explicit narrow) leaves this arm alone. */ +export function promiseOrUnitUnionOf(lowerer: Lowerer, t: IrType): MaybePromiseUnion | null { + const armed = promiseArmedUnionOf(lowerer, t); + if (!armed) return null; + if (!armed.def.arms.every((a, i) => i === armed.promiseTag || isUnitType(a))) return null; + return armed; +} + /** Any union with exactly one promise arm: a maybe-promise union or a * promise-or-unit union (`Promise | void`). An async `return` * resolves both the same way: adopt the promise arm, fulfill otherwise. */ diff --git a/tests/corpus/promise-catch-optional-chain.ts b/tests/corpus/promise-catch-optional-chain.ts new file mode 100644 index 000000000..638fb8417 --- /dev/null +++ b/tests/corpus/promise-catch-optional-chain.ts @@ -0,0 +1,64 @@ +// `?.` reaches a promise method through a receiver that may be nullish: +// `p?.catch(handler)` guards the method read (JS calls nothing when `p` is +// nullish), and the chain tail `f()?.then(..).catch(..)` guards the whole +// tail. The guarded call itself is an ordinary promise method call, so the +// handler typing sees the promise arm, and the chain result is +// `Promise<..> | undefined`. + +interface Client { + name: string; +} + +class Box { + opening: Promise | undefined; + pending: Promise | undefined; + + constructor(opening: Promise | undefined, pending: Promise | undefined) { + this.opening = opening; + this.pending = pending; + } + + async run(): Promise { + this.opening?.catch(() => undefined); + console.log("1 opening guarded"); + await this.pending?.catch(() => undefined); + console.log("2 pending guarded"); + } +} + +function open(name: string): Promise { + return Promise.resolve({ name }); +} + +function pick(flag: boolean): Promise | undefined { + return flag ? Promise.resolve("value") : undefined; +} + +function tail(flag: boolean): Promise { + return pick(flag)! + .then((value) => { + console.log("tail then", value); + return value.length; + }) + .catch(() => undefined); +} + +function guardedTail(flag: boolean): void { + pick(flag)?.then((value) => { + console.log("guarded then", value); + return value.length; + }); + console.log("3 tail attached", flag); +} + +const present = new Box(open("digest"), Promise.resolve()); +const absent = new Box(undefined, undefined); +present + .run() + .then(() => absent.run()) + .then(() => tail(true)) + .then(() => { + guardedTail(true); + guardedTail(false); + }) + .then(() => console.log("done")); From a4a84f59da234c7401eed409363ccf19d3d02301 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 27 Oct 2026 21:12:28 -0700 Subject: [PATCH 3/6] Resolve a promise-or-unit operand in `Promise.resolve` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Promise.resolve(p)` over `Promise | void` — an async callback's declared result — was refused: the maybe-promise detection required every non-promise arm to carry a value, while a nullish arm is exactly what `Promise.resolve` fulfills as undefined. `promiseOrUnitUnionOf` names that shape, and the resolution reuses the existing adopt-or- fulfill path, so the promise arm passes through unchanged (`Promise.resolve(p) === p`). Co-Authored-By: Claude Opus 5.5 --- .../frontend/lowering/builtins/promises.ts | 16 +++++++-- .../corpus/promise-resolve-promise-or-unit.ts | 34 +++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 tests/corpus/promise-resolve-promise-or-unit.ts diff --git a/packages/compiler/src/frontend/lowering/builtins/promises.ts b/packages/compiler/src/frontend/lowering/builtins/promises.ts index c3b2cd479..fdd16da90 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promises.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promises.ts @@ -5,7 +5,11 @@ import * as ts from "../../ts7/adapter.js"; import type { Lowerer } from "../lowerer.js"; import { countedFor, varRef } from "../../../ir/build.js"; import { arrayValueStore, optionalElementValue, primitiveElement } from "../array-values.js"; -import { maybePromiseResolve, maybePromiseUnionOf } from "../maybe-promise.js"; +import { + maybePromiseResolve, + maybePromiseUnionOf, + promiseOrUnitUnionOf, +} from "../maybe-promise.js"; import { isJsSourceFile, locOf } from "../../program.js"; import { BOOL, @@ -670,11 +674,17 @@ export function lowerPromiseStaticCall( if (v.type.kind !== "promise") lowerer.badType(argNode, argT); return v; } - const maybePromise = argIr ? maybePromiseUnionOf(lowerer, argIr) : null; + // A promise-or-unit union (`Promise | void`, + // `Promise | undefined`) resolves exactly like a maybe-promise + // one: Node adopts a promise operand and fulfills every other value + // as itself (a nullish arm fulfills undefined). + const maybePromise = argIr + ? (maybePromiseUnionOf(lowerer, argIr) ?? promiseOrUnitUnionOf(lowerer, argIr)) + : null; if (maybePromise) { const resultT = lowerer.mapTypeOf(lowerer.typeOf(call)); const v = lowerer.lowerExpr(argNode); - const mp = maybePromiseUnionOf(lowerer, v.type); + const mp = maybePromiseUnionOf(lowerer, v.type) ?? promiseOrUnitUnionOf(lowerer, v.type); const resolved = resultT?.kind === "promise" && mp ? maybePromiseResolve(lowerer, v, mp, resultT, loc) diff --git a/tests/corpus/promise-resolve-promise-or-unit.ts b/tests/corpus/promise-resolve-promise-or-unit.ts new file mode 100644 index 000000000..209021ab0 --- /dev/null +++ b/tests/corpus/promise-resolve-promise-or-unit.ts @@ -0,0 +1,34 @@ +// `Promise.resolve(u)` over a promise-or-unit union (`Promise | +// void`): Node adopts a promise operand — the SAME promise comes +// back — and fulfills a nullish one as undefined. The settled turn of the +// adopted promise is unchanged, so a handler chain observes the same +// microtask order either way. + +let runs = 0; + +async function step(label: string): Promise { + runs++; + await Promise.resolve(); + console.log("step", label, runs); +} + +// A callback whose declared result is `Promise | void`: the +// async path answers a promise, the sync one answers undefined. +function handler(flag: boolean): Promise | void { + if (flag) return step("async"); + runs++; + console.log("sync handler", runs); +} + +function call(flag: boolean): Promise { + return Promise.resolve(handler(flag)).then(() => console.log("call done", flag)); +} + +const pending: Promise | void = Promise.resolve(); +console.log("identity", Promise.resolve(pending) === pending); + +console.log("start"); +call(true) + .then(() => call(false)) + .then(() => call(true)) + .then(() => console.log("runs", runs)); From 0496f0eb2553b064837d87c9ed01345028af3170 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 27 Oct 2026 21:12:28 -0700 Subject: [PATCH 4/6] Lower `Array.from(typedArray, mapper)` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Array.from(bytes, f)` had no lowering — only the mapper-less `toArray` — so every mapper over a typed array fenced. It lifts a loop that reads each element through the typed array itself, because Node iterates live: a mapper that writes the source is visible to the indexes it has not visited yet. Co-Authored-By: Claude Opus 5.5 --- .../lowering/containers/array-construction.ts | 121 ++++++++++++++++++ tests/corpus/array-from-typed-array-mapper.ts | 25 ++++ 2 files changed, 146 insertions(+) create mode 100644 tests/corpus/array-from-typed-array-mapper.ts diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index cadee368a..2a3ad64c0 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -1158,6 +1158,37 @@ export function lowerArrayFromCall( loc, }; } + // `Array.from(typedArray, mapper)`: the element read is live (a + // mapper that writes the source changes later reads, as in Node), + // so this is a loop over the typed array, not a toArray snapshot. + if (src.type.kind === "bytes" && args.length === 2) { + const callback = lowerArrayCallback(lowerer, args[1]!, [F64], arrayOf(F64)); + if (callback.arity > 2) + lowerer.noLowering("Array.from mapper with an array parameter", args[1]!); + const fnRet = callback.fnArg.type.ret; + if (fnRet.kind === "void" || fnRet.kind === "func") + lowerer.badType(call, lowerer.typeOf(call)); + requireProducedArrayElement(lowerer, call, "'Array.from(typedArray, mapper)'", fnRet); + const outElem = callbackArrayElement(lowerer, call, fnRet); + // The element kind rides in the key because the lifted helper's + // source parameter carries it (the reads themselves are always f64). + const key = `fromBytes:${src.type.elem}:${typeKey(outElem)}:${typeKey(fnRet)}:${callback.arity}`; + let helper = lowerer.arrHofHelpers.get(key); + if (!helper) { + helper = keyedHelperName(lowerer, "%arr.fromBytes", key); + lowerer.arrHofHelpers.set(key, helper); + lowerer.liftedFns.push( + buildArrayFromBytesFn(lowerer, helper, src.type, outElem, fnRet, callback.arity, loc), + ); + } + return { + kind: "call", + callee: helper, + args: [src, callback.fnArg], + type: arrayOf(outElem), + loc, + }; + } // A union of typed arrays and number arrays (indexed-unions.ts). const indexedArms = indexedUnionArms(lowerer, src.type); if (indexedArms && args.length === 1) @@ -1320,6 +1351,96 @@ function buildArrayFromArrayFn( }; } +/** The `Array.from(typedArray, mapper)` generation loop: every index is + * read through the typed array itself (live, like Node's iteration), so + * a mapper that writes the source is visible to the indexes it has not + * visited yet. */ +function buildArrayFromBytesFn( + lowerer: Lowerer, + name: string, + bytesT: IrType & { kind: "bytes" }, + outElem: IrType, + fnRet: IrType, + arity: number, + loc: SrcLoc, +): IrFunction { + const outT = arrayOf(outElem); + const fnT = funcOf([F64, F64].slice(0, arity), fnRet); + const a = varRef("a.0", bytesT, loc); + const i = varRef("i.0", F64, loc); + const out = varRef("out.0", outT, loc); + // The index parameter is the second one, never read for an index the + // element covers, so an element-only mapper drops it. + const reads: IrExpr[] = [ + { kind: "bytesIntrinsic", method: "get", receiver: a, args: [i], type: F64, loc }, + i, + ]; + const mapped: IrExpr = { + kind: "callValue", + callee: varRef("f.0", fnT, loc), + args: reads.slice(0, arity), + type: fnRet, + loc, + }; + const held = !typeEquals(fnRet, outElem); + const value: IrExpr = held ? varRef("v.0", fnRet, loc) : mapped; + return { + name, + params: [ + { localId: "a.0", name: "a", type: bytesT }, + { localId: "f.0", name: "f", type: fnT }, + ], + returnType: outT, + locals: [ + { id: "a.0", name: "a", type: bytesT, mutable: true }, + { id: "f.0", name: "f", type: fnT, mutable: true }, + { id: "out.0", name: "out", type: outT, mutable: false }, + { id: "i.0", name: "i", type: F64, mutable: true }, + ...(held ? [{ id: "v.0", name: "v", type: fnRet, mutable: false }] : []), + ], + body: [ + { + kind: "varDecl", + localId: "out.0", + init: { kind: "arrayLit", elems: [], type: outT, loc }, + loc, + }, + { kind: "varDecl", localId: "i.0", init: numLit(0, loc), loc }, + { + kind: "while", + cond: { + kind: "bin", + op: "<", + left: i, + right: { + kind: "bytesIntrinsic", + method: "length", + receiver: a, + args: [], + type: F64, + loc, + }, + type: BOOL, + loc, + }, + body: [ + ...(held ? [{ kind: "varDecl", localId: "v.0", init: mapped, loc } as const] : []), + arrayValueStore(lowerer, out, i, value, outElem, loc), + { + kind: "assign", + localId: "i.0", + value: { kind: "bin", op: "+", left: i, right: numLit(1, loc), type: F64, loc }, + loc, + }, + ], + loc, + }, + { kind: "return", value: out, loc }, + ], + loc, + }; +} + /** `Array.from(s)` / `[...s]` on a STRING: the code-point split into a * fresh string[] (scr_str_code_points). */ export function strCharsCall(src: IrExpr, loc: SrcLoc): IrExpr { diff --git a/tests/corpus/array-from-typed-array-mapper.ts b/tests/corpus/array-from-typed-array-mapper.ts new file mode 100644 index 000000000..a6ee1012e --- /dev/null +++ b/tests/corpus/array-from-typed-array-mapper.ts @@ -0,0 +1,25 @@ +// `Array.from(typedArray, mapper)`: the mapper receives each element as a +// number and its index, and the element reads are LIVE — iteration reads +// index i after visiting i-1, so a mapper that writes the source changes +// the values the loop has not visited yet. The result is a plain array of +// the mapper's results. + +const bytes = new Uint8Array([104, 105, 33]); +console.log(Array.from(bytes, (byte) => String.fromCharCode(byte)).join("")); +console.log(Array.from(bytes, (byte, index) => `${index}:${byte}`).join(",")); + +const live = new Uint8Array([1, 2, 3]); +const doubled = Array.from(live, (value, index) => { + if (index === 0) live[2] = 30; + return value * 2; +}); +console.log(doubled.join(",")); + +// A mapper that ignores its parameters, and an empty source. +console.log(Array.from(new Uint8Array(0), () => 7).length); +console.log(Array.from(bytes, () => "x").join("")); + +// Element identity survives the map: objects are not copied. +const words = new Uint8Array([1, 2]); +const boxed = Array.from(words, (word) => ({ word })); +console.log(boxed.length, boxed[1]!.word); From b8bb5bfdb04c8e51da46c4b40276fe258a88f62b Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 27 Oct 2026 21:24:37 -0700 Subject: [PATCH 5/6] Record the new corpus programs' CI costs Co-Authored-By: Claude Opus 5.5 --- tests/harness/corpus-costs/array-from-typed-array-mapper.ts.json | 1 + tests/harness/corpus-costs/promise-catch-optional-chain.ts.json | 1 + .../harness/corpus-costs/promise-resolve-promise-or-unit.ts.json | 1 + tests/harness/corpus-costs/promise-void-catch-undefined.ts.json | 1 + 4 files changed, 4 insertions(+) create mode 100644 tests/harness/corpus-costs/array-from-typed-array-mapper.ts.json create mode 100644 tests/harness/corpus-costs/promise-catch-optional-chain.ts.json create mode 100644 tests/harness/corpus-costs/promise-resolve-promise-or-unit.ts.json create mode 100644 tests/harness/corpus-costs/promise-void-catch-undefined.ts.json diff --git a/tests/harness/corpus-costs/array-from-typed-array-mapper.ts.json b/tests/harness/corpus-costs/array-from-typed-array-mapper.ts.json new file mode 100644 index 000000000..094458e73 --- /dev/null +++ b/tests/harness/corpus-costs/array-from-typed-array-mapper.ts.json @@ -0,0 +1 @@ +{ "plain": 1400, "san": 1200 } diff --git a/tests/harness/corpus-costs/promise-catch-optional-chain.ts.json b/tests/harness/corpus-costs/promise-catch-optional-chain.ts.json new file mode 100644 index 000000000..ba681e2a0 --- /dev/null +++ b/tests/harness/corpus-costs/promise-catch-optional-chain.ts.json @@ -0,0 +1 @@ +{ "plain": 1000, "san": 900 } diff --git a/tests/harness/corpus-costs/promise-resolve-promise-or-unit.ts.json b/tests/harness/corpus-costs/promise-resolve-promise-or-unit.ts.json new file mode 100644 index 000000000..035725f07 --- /dev/null +++ b/tests/harness/corpus-costs/promise-resolve-promise-or-unit.ts.json @@ -0,0 +1 @@ +{ "plain": 900, "san": 900 } diff --git a/tests/harness/corpus-costs/promise-void-catch-undefined.ts.json b/tests/harness/corpus-costs/promise-void-catch-undefined.ts.json new file mode 100644 index 000000000..a72de5c97 --- /dev/null +++ b/tests/harness/corpus-costs/promise-void-catch-undefined.ts.json @@ -0,0 +1 @@ +{ "plain": 1600, "san": 900 } From 4e18ec933b721e03c6f5d8706b78c92d40b2258a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Wed, 28 Oct 2026 08:28:40 -0500 Subject: [PATCH 6/6] Record the preflight and order baselines of the new corpus programs array-from-typed-array-mapper.ts, promise-catch-optional-chain.ts, promise-resolve-promise-or-unit.ts and promise-void-catch-undefined.ts each load only themselves and report no preflight diagnostics. --- .../tests/corpus/array-from-typed-array-mapper.ts.json | 6 ++++++ .../tests/corpus/promise-catch-optional-chain.ts.json | 6 ++++++ .../tests/corpus/promise-resolve-promise-or-unit.ts.json | 6 ++++++ .../tests/corpus/promise-void-catch-undefined.ts.json | 6 ++++++ 4 files changed, 24 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/array-from-typed-array-mapper.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/promise-catch-optional-chain.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/promise-resolve-promise-or-unit.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/promise-void-catch-undefined.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/array-from-typed-array-mapper.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/array-from-typed-array-mapper.ts.json new file mode 100644 index 000000000..6862c988b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/array-from-typed-array-mapper.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/array-from-typed-array-mapper.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/promise-catch-optional-chain.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/promise-catch-optional-chain.ts.json new file mode 100644 index 000000000..5ff12d854 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/promise-catch-optional-chain.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/promise-catch-optional-chain.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/promise-resolve-promise-or-unit.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/promise-resolve-promise-or-unit.ts.json new file mode 100644 index 000000000..c85693cc2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/promise-resolve-promise-or-unit.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/promise-resolve-promise-or-unit.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/promise-void-catch-undefined.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/promise-void-catch-undefined.ts.json new file mode 100644 index 000000000..f9ad9a7b3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/promise-void-catch-undefined.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/promise-void-catch-undefined.ts" + ], + "diags": [] +}