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

@ -1062,15 +1062,179 @@ describe("unsupported promise surface", () => {
})
test("unknown Promise statics list what is available", async () => {
const diagnostic = await error(`return await Promise.any([tools.host.sleepy({ id: 1 })])`)
expect(diagnostic.message).toContain("Promise.any is not available")
expect(diagnostic.message).toContain("Promise.allSettled")
})
test("new Promise(...) points at tool calls instead", async () => {
const diagnostic = await error(`return new Promise((resolve) => resolve(1))`)
expect(diagnostic.kind).toBe("UnsupportedSyntax")
expect(diagnostic.message).toContain("new Promise(...) is not supported")
expect(diagnostic.message).toContain("already return promises")
const diagnostic = await error(`return await Promise.withResolvers()`)
expect(diagnostic.message).toContain("Promise.withResolvers is not available")
expect(diagnostic.message).toContain("Promise.any")
})
})
describe("Promise.any", () => {
test("first tool success wins; failing and losing calls are handled silently", async () => {
const trace = makeTrace()
const result = await run(
`
const winner = await Promise.any([
tools.host.fail({}),
tools.host.sleepy({ id: 1, ms: 5 }),
tools.host.sleepy({ id: 2, ms: 60000 }),
])
return winner
`,
{ trace },
)
expect(result.ok).toBe(true)
if (!result.ok) return
expect(result.value).toBe(1)
// The slow loser stays execution-owned and is interrupted at completion; the tool
// failure was observed by the aggregate, so no rejection warning survives.
expect(result.warnings).toBeUndefined()
expect(trace.interrupted).toBe(1)
})
test("all members failing rejects with catch-normalized reasons in input order", async () => {
expect(
await value(`
try {
await Promise.any([tools.host.fail({}), Promise.reject("plain")])
return "fulfilled"
} catch (error) {
return [error.name, error.errors.map((reason) => reason.message ?? reason)]
}
`),
).toEqual(["AggregateError", ["Lookup refused", "plain"]])
})
test("settles one reaction turn after its deciding member, as in V8", async () => {
expect(await value(`return await Promise.race([Promise.any([Promise.resolve(1)]), Promise.resolve(2)])`)).toBe(2)
})
test("a tie is decided by settlement order, not input order", async () => {
// Handlers run in attach order, so `first` settles before `second` and wins
// despite its later input position - as in real JS.
expect(
await value(`
const first = Promise.resolve().then(() => "one")
const second = Promise.resolve().then(() => "two")
return await Promise.any([second, first])
`),
).toBe("one")
})
test("an abandoned rejecting aggregate is interrupted silently at the return", async () => {
const result = await run(`
Promise.any([Promise.reject(new Error("boom"))])
return "done"
`)
expect(result.ok).toBe(true)
if (!result.ok) return
expect(result.value).toBe("done")
expect(result.warnings).toBeUndefined()
})
})
describe("promise construction", () => {
test("a deferred gate coordinates tool results across async functions", async () => {
expect(
await value(`
let openGate
const gate = new Promise((resolve) => { openGate = resolve })
const worker = (async () => {
const id = await gate
return id * 2
})()
openGate(await tools.host.sleepy({ id: 21, ms: 5 }))
return await worker
`),
).toBe(42)
})
test("the .then(resolve) bridge settles a constructed promise", async () => {
expect(
await value(`
const bridged = new Promise((resolve, reject) => {
tools.host.sleepy({ id: 7, ms: 5 }).then(resolve, reject)
})
return await bridged
`),
).toBe(7)
})
test("constructed promises participate in combinators", async () => {
expect(
await value(`
let settle
const manual = new Promise((resolve) => { settle = resolve })
const race = Promise.race([manual, tools.host.sleepy({ id: 3, ms: 60000 })])
const all = Promise.all([manual, "plain"])
const any = Promise.any([manual, new Promise(() => {})])
settle("manual")
return [await race, await all, await any]
`),
).toEqual(["manual", ["manual", "plain"], "manual"])
})
test("resolving with a pending promise adopts its later settlement", async () => {
expect(
await value(`
let innerResolve, innerReject
const adopted = new Promise((resolve) => resolve(new Promise((resolve) => { innerResolve = resolve })))
const adoptedRejection = new Promise((resolve) => resolve(new Promise((_, reject) => { innerReject = reject })))
innerResolve("later")
innerReject("bad")
try {
return [await adopted, await adoptedRejection]
} catch (reason) {
return [await adopted, reason]
}
`),
).toEqual(["later", "bad"])
})
test("an async executor's post-await resolve settles the promise", async () => {
expect(
await value(`
const result = new Promise(async (resolve) => {
const id = await tools.host.sleepy({ id: 5, ms: 5 })
resolve(id * 2)
})
return await result
`),
).toBe(10)
})
test("a never-settled promise is abandoned silently at the return", async () => {
const result = await run(`
const forever = new Promise(() => {})
forever.then(() => {})
return "done"
`)
expect(result.ok).toBe(true)
if (!result.ok) return
expect(result.value).toBe("done")
expect(result.warnings).toBeUndefined()
})
test("an un-awaited constructed rejection is reported like any unhandled rejection", async () => {
const result = await run(`
new Promise((_, reject) => reject(new Error("dropped")))
await Promise.resolve()
await Promise.resolve()
return "done"
`)
expect(result.ok).toBe(true)
if (!result.ok) return
expect(result.value).toBe("done")
expect(result.warnings).toHaveLength(1)
expect(result.warnings?.[0].message).toContain("Unhandled rejection")
expect(result.warnings?.[0].message).toContain("dropped")
})
test("resolver functions cannot cross the data boundary", async () => {
const diagnostic = await error(`
let escaped
new Promise((resolve) => { escaped = resolve })
return { escaped }
`)
expect(diagnostic.kind).toBe("InvalidDataValue")
})
})