feat(codemode): support Promise.any and new Promise construction (#36339)

This commit is contained in:
Aiden Cline 2026-07-10 20:46:13 -05:00 committed by GitHub
commit fe09a2e9b7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 741 additions and 47 deletions

View file

@ -573,13 +573,14 @@ describe("Test262 async functions and await", () => {
})
describe("Test262 expected Promise conformance", () => {
for (const name of ["all", "allSettled", "race"] as const) {
for (const name of ["all", "allSettled", "race", "any"] as const) {
test(`Promise.${name} rejects invalid input with TypeError`, async () => {
// Sources:
// test/built-ins/Promise/all/S25.4.4.1_A3.1_T1.js
// test/built-ins/Promise/all/S25.4.4.1_A3.1_T2.js
// test/built-ins/Promise/allSettled/iter-arg-is-number-reject.js
// test/built-ins/Promise/race/iter-arg-is-number-reject.js
// test/built-ins/Promise/any/iter-arg-is-number-reject.js
expect(
await value(`
try {
@ -595,7 +596,7 @@ describe("Test262 expected Promise conformance", () => {
})
}
test.failing("Promise.all consumes sparse positions as undefined", async () => {
test("Promise.all consumes sparse positions as undefined", async () => {
// Source: test/built-ins/Array/from/from-array.js (array iterator hole behavior)
expect(
await value(`
@ -607,7 +608,7 @@ describe("Test262 expected Promise conformance", () => {
).toEqual([2, true, 1])
})
test.failing("Promise.allSettled consumes sparse positions as undefined", async () => {
test("Promise.allSettled consumes sparse positions as undefined", async () => {
// Source: test/built-ins/Array/from/from-array.js (array iterator hole behavior)
expect(
await value(`
@ -619,7 +620,7 @@ describe("Test262 expected Promise conformance", () => {
).toEqual([2, "fulfilled", true, { status: "fulfilled", value: 1 }])
})
test.failing("Promise.race consumes a sparse first position as undefined", async () => {
test("Promise.race consumes a sparse first position as undefined", async () => {
// Source: test/built-ins/Array/from/from-array.js (array iterator hole behavior)
expect(
await value(`
@ -630,6 +631,17 @@ describe("Test262 expected Promise conformance", () => {
).toBe(true)
})
test("Promise.any consumes a sparse first position as an undefined fulfillment", async () => {
// Source: test/built-ins/Array/from/from-array.js (array iterator hole behavior)
expect(
await value(`
const input = []
input[1] = Promise.reject("loses")
return (await Promise.any(input)) === undefined
`),
).toBe(true)
})
test("Promise.all settles after reactions attached to its inputs", async () => {
// Sources:
// test/built-ins/Promise/all/S25.4.4.1_A7.2_T1.js
@ -1002,3 +1014,410 @@ describe("Test262 expected Promise conformance", () => {
).toBe(true)
})
})
describe("Test262 Promise.any", () => {
test("is a callable static that returns a promise", async () => {
// Sources:
// test/built-ins/Promise/any/is-function.js
// test/built-ins/Promise/any/returns-promise.js
expect(
await value(`
const promise = Promise.any([1])
return [typeof Promise.any, promise instanceof Promise, await promise]
`),
).toEqual(["function", true, 1])
})
test("fulfills with the first fulfilled member, ignoring rejections", async () => {
// Sources:
// test/built-ins/Promise/any/resolved-sequence-mixed.js
// test/built-ins/Promise/any/resolved-sequence-with-rejections.js
// test/built-ins/Promise/any/reject-ignored-immed.js
expect(
await value(`
return [
await Promise.any([Promise.reject("a"), Promise.resolve(1), Promise.resolve(2)]),
await Promise.any([Promise.reject("a"), "plain", Promise.reject("b")]),
]
`),
).toEqual([1, "plain"])
})
test("a fulfillment wins over a later rejection of another member", async () => {
// Sources:
// test/built-ins/Promise/any/resolve-ignores-late-rejection.js
// test/built-ins/Promise/any/resolve-ignores-late-rejection-deferred.js
expect(
await value(`
let rejectLate
const late = new Promise((_, reject) => { rejectLate = reject })
const result = await Promise.any([late, Promise.resolve("won")])
rejectLate("too late")
return result
`),
).toBe("won")
})
test("rejects with an AggregateError carrying the reasons in input order", async () => {
// Sources:
// test/built-ins/Promise/any/reject-all-mixed.js
// test/built-ins/Promise/any/reject-immed.js
// test/built-ins/Promise/any/reject-deferred.js
expect(
await value(`
let rejectLate
const late = new Promise((_, reject) => { rejectLate = reject })
const aggregate = Promise.any([Promise.reject("first"), late, Promise.reject("third")])
rejectLate("second")
try {
await aggregate
return "fulfilled"
} catch (error) {
return {
isAggregate: error instanceof AggregateError,
isError: error instanceof Error,
name: error.name,
message: error.message,
errors: error.errors,
}
}
`),
).toEqual({
isAggregate: true,
isError: true,
name: "AggregateError",
message: "All promises were rejected",
errors: ["first", "second", "third"],
})
})
test("rejects an empty input with an empty AggregateError", async () => {
// Source: test/built-ins/Promise/any/iter-arg-is-empty-iterable-reject.js
expect(
await value(`
try {
await Promise.any([])
return "fulfilled"
} catch (error) {
return [error instanceof AggregateError, error.errors.length]
}
`),
).toEqual([true, 0])
})
test("consumes a string input as its characters", async () => {
// Source: test/built-ins/Promise/any/iter-arg-is-string-resolve.js
expect(await value(`return await Promise.any("abc")`)).toBe("a")
})
test("rejects an empty string input with an empty AggregateError", async () => {
// Source: test/built-ins/Promise/any/iter-arg-is-empty-string-reject.js
expect(
await value(`
try {
await Promise.any("")
return "fulfilled"
} catch (error) {
return [error instanceof AggregateError, error.errors.length]
}
`),
).toEqual([true, 0])
})
test("fulfills with the first member that does not reject", async () => {
// Sources:
// test/built-ins/Promise/any/resolve-from-reject-catch.js
// test/built-ins/Promise/any/resolve-from-resolve-reject-catch.js
expect(
await value(`
return await Promise.any([
Promise.reject("a"),
new Promise((resolve, reject) => reject("b")),
Promise.all([Promise.reject("c")]),
Promise.resolve(Promise.reject("d").catch((reason) => reason)),
])
`),
).toBe("d")
})
test("settles after reactions attached to its inputs", async () => {
// Source: test/built-ins/Promise/any/resolved-sequence.js
expect(
await value(`
const sequence = [1]
const input = Promise.resolve(1)
const aggregate = Promise.any([input])
aggregate.then(() => sequence.push(4))
input.then(() => sequence.push(3)).then(() => sequence.push(5))
sequence.push(2)
await aggregate
await Promise.resolve()
return sequence
`),
).toEqual([1, 2, 3, 4, 5])
})
})
describe("Test262 AggregateError", () => {
test("constructs from an errors collection and an optional message", async () => {
// Sources:
// test/built-ins/AggregateError/errors-iterabletolist.js
// test/built-ins/AggregateError/message-undefined-no-prop.js
expect(
await value(`
const input = ["x", "y"]
const withMessage = new AggregateError(input, "msg")
const bare = new AggregateError([])
return [
withMessage.name,
withMessage.message,
withMessage.errors,
withMessage.errors !== input,
withMessage instanceof AggregateError,
withMessage instanceof Error,
bare.message,
bare.errors,
]
`),
).toEqual(["AggregateError", "msg", ["x", "y"], true, true, true, "", []])
})
test("rejects a non-collection errors argument with TypeError", async () => {
// Source: test/built-ins/AggregateError/errors-iterabletolist-failures.js
expect(
await value(`
try {
new AggregateError(42)
return "constructed"
} catch (error) {
return error.name
}
`),
).toBe("TypeError")
})
test("is callable without new", async () => {
// Source: test/built-ins/AggregateError/newtarget-is-undefined.js
expect(
await value(`
const error = AggregateError(["x"], "m")
return [error instanceof AggregateError, error instanceof Error, error.name, error.message, error.errors]
`),
).toEqual([true, true, "AggregateError", "m", ["x"]])
})
test("coerces a non-string message to a string", async () => {
// Source: test/built-ins/AggregateError/message-method-prop-cast.js (value coercion only; the
// upstream object-with-toString case is omitted because the sandbox has no user toString dispatch)
expect(
await value(`
return [
new AggregateError([], 42).message,
new AggregateError([], false).message,
new AggregateError([], true).message,
new AggregateError([], null).message,
]
`),
).toEqual(["42", "false", "true", "null"])
})
})
describe("Test262 Promise constructor", () => {
test("constructs a promise, handing the executor callable resolve/reject", async () => {
// Sources:
// test/built-ins/Promise/constructor.js
// test/built-ins/Promise/exec-args.js
expect(
await value(`
let observed
const promise = new Promise((resolve, reject) => {
observed = [typeof resolve, typeof reject]
resolve("done")
})
return [promise instanceof Promise, observed, await promise]
`),
).toEqual([true, ["function", "function"], "done"])
})
test("a missing or non-callable executor is a TypeError", async () => {
// Source: test/built-ins/Promise/executor-not-callable.js
expect(
await value(`
const outcomes = []
for (const make of [() => new Promise(), () => new Promise(1), () => new Promise({})]) {
try {
make()
outcomes.push("constructed")
} catch (error) {
outcomes.push(error.name)
}
}
return outcomes
`),
).toEqual(["TypeError", "TypeError", "TypeError"])
})
test("resolves immediately or later through an escaping resolver", async () => {
// Sources:
// test/built-ins/Promise/resolve-non-thenable-immed.js
// test/built-ins/Promise/resolve-non-thenable-deferred.js
// test/built-ins/Promise/create-resolving-functions-resolve.js
expect(
await value(`
let settle
const deferred = new Promise((resolve) => { settle = resolve })
const immediate = new Promise((resolve) => resolve("now"))
settle("later")
return [await immediate, await deferred]
`),
).toEqual(["now", "later"])
})
test("rejects through reject and through an abrupt executor completion", async () => {
// Sources:
// test/built-ins/Promise/reject-via-fn-immed.js
// test/built-ins/Promise/reject-via-abrupt.js
expect(
await value(`
const observe = async (promise) => {
try { return ["fulfilled", await promise] } catch (reason) { return ["rejected", reason.message ?? reason] }
}
return [
await observe(new Promise((_, reject) => reject("nope"))),
await observe(new Promise(() => { throw new Error("boom") })),
]
`),
).toEqual([
["rejected", "nope"],
["rejected", "boom"],
])
})
test("only the first settlement counts", async () => {
// Sources:
// test/built-ins/Promise/reject-ignored-via-fn-immed.js
// test/built-ins/Promise/resolve-ignored-via-fn-immed.js
expect(
await value(`
return [
await new Promise((resolve, reject) => { resolve("first"); reject("second"); resolve("third") }),
await new Promise((resolve) => { resolve(resolve("inner") === undefined ? "unreached" : "also unreached") }),
]
`),
).toEqual(["first", "inner"])
})
test("escaped resolvers keep first-settle-wins in both directions", async () => {
// Sources:
// test/built-ins/Promise/reject-ignored-via-fn-deferred.js
// test/built-ins/Promise/resolve-ignored-via-fn-deferred.js
expect(
await value(`
let resolveRejected, rejectRejected
const rejected = new Promise((resolve, reject) => { resolveRejected = resolve; rejectRejected = reject })
rejectRejected("first")
const lateResolve = resolveRejected("late")
let resolveFulfilled, rejectFulfilled
const fulfilled = new Promise((resolve, reject) => { resolveFulfilled = resolve; rejectFulfilled = reject })
resolveFulfilled()
const lateReject = rejectFulfilled(new Promise(() => {}))
try {
await rejected
return "fulfilled"
} catch (reason) {
return [reason, lateResolve === undefined, (await fulfilled) === undefined, lateReject === undefined]
}
`),
).toEqual(["first", true, true, true])
})
test("a queued reaction chain observes a later rejection through a handler-less then", async () => {
// Sources:
// test/built-ins/Promise/reject-via-fn-immed-queue.js
// test/built-ins/Promise/reject-via-fn-deferred-queue.js
// test/built-ins/Promise/reject-via-abrupt-queue.js
expect(
await value(`
const observe = (promise) => promise.then(() => "wrong").then(() => "also wrong", (reason) => "caught:" + reason)
let reject
const deferred = new Promise((_, r) => { reject = r })
const chained = observe(deferred)
reject("boom")
return [
await observe(new Promise((_, r) => r("immed"))),
await chained,
await observe(new Promise(() => { throw "abrupt" })),
]
`),
).toEqual(["caught:immed", "caught:boom", "caught:abrupt"])
})
test("an exception after resolve is ignored", async () => {
// Source: test/built-ins/Promise/exception-after-resolve-in-executor.js
expect(await value(`return await new Promise((resolve) => { resolve("kept"); throw new Error("dropped") })`)).toBe(
"kept",
)
})
test("resolving with a promise adopts its settlement", async () => {
// Sources:
// test/built-ins/Promise/resolve-thenable-immed.js (promise-adoption portion)
// test/built-ins/Promise/all/S25.4.4.1_A2.3_T1.js (resolution adoption semantics)
expect(
await value(`
const adoptedValue = await new Promise((resolve) => resolve(Promise.resolve("adopted")))
try {
await new Promise((resolve) => resolve(Promise.reject("bad")))
return [adoptedValue, "fulfilled"]
} catch (reason) {
return [adoptedValue, reason]
}
`),
).toEqual(["adopted", "bad"])
})
test("resolving with the promise itself rejects with TypeError", async () => {
// Source: test/built-ins/Promise/resolve-self.js
expect(
await value(`
let settle
const promise = new Promise((resolve) => { settle = resolve })
settle(promise)
try {
await promise
return "fulfilled"
} catch (error) {
return error.name
}
`),
).toBe("TypeError")
})
test("executor runs synchronously before the constructor returns", async () => {
// Source: test/built-ins/Promise/executor-call-context-strict.js (synchronous Call(executor) step)
expect(
await value(`
const sequence = []
sequence.push("before")
new Promise((resolve) => { sequence.push("executor"); resolve() })
sequence.push("after")
return sequence
`),
).toEqual(["before", "executor", "after"])
})
test.failing("calling Promise without new throws TypeError", async () => {
// Source: test/built-ins/Promise/undefined-newtarget.js
// The sandbox currently reports a generic Error ("Only tools are callable in CodeMode.").
expect(
await value(`
try {
Promise(() => {})
return "called"
} catch (error) {
return error.name
}
`),
).toBe("TypeError")
})
})