mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Await a union whose promise arm settles a union
`Promise<A | B> | undefined` awaits as Awaited of the operand, which the maybe-promise lowering already computes: it awaits the narrowed promise arm and coerces its settled union, and the non-promise arms take JS's one microtask hop. The await lowering refused any union inner instead, so the promise-or-unit shape now goes through that same path; a union whose promise arm has no combined result keeps the SC1090 fence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a0a6910e4e
commit
99ebf7501d
@@ -26,7 +26,11 @@ import { checkedPrivateReceiver, localPrivateBrandClass } from "./private-brands
|
||||
import { staticCondition } from "./static-typeof.js";
|
||||
import { constituentTypes } from "../ts7/checker.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
import { lowerMaybePromiseAwait, maybePromiseUnionOf } from "./maybe-promise.js";
|
||||
import {
|
||||
lowerMaybePromiseAwait,
|
||||
maybePromiseUnionOf,
|
||||
promiseArmedUnionOf,
|
||||
} from "./maybe-promise.js";
|
||||
import { checkedClassUnionAssertion } from "./class-assertions.js";
|
||||
import {
|
||||
BIGINT_T,
|
||||
@@ -668,9 +672,23 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if (inner.kind === "union" || inner.kind === "dyn") {
|
||||
// A union inner would need an arm-wise re-tag into the result
|
||||
|
||||
if (inner.kind === "union") {
|
||||
// A UNION inner (`Promise<A | B> | undefined`): the awaited
|
||||
// result is the checker's own `Awaited` of the operand, which
|
||||
// the maybe-promise lowering already produces — it awaits the
|
||||
// narrowed promise arm and coerces its settled union, and the
|
||||
// unit arms keep JS's one-microtask hop. Only the promise-or-
|
||||
// unit shape reaches here, so the maybe-promise union is the
|
||||
// same union with its only data arms being units.
|
||||
const armed = promiseArmedUnionOf(lowerer, value.type);
|
||||
if (armed) return lowerMaybePromiseAwait(lowerer, expr, value, armed, loc);
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
expr,
|
||||
`awaiting '${lowerer.fmt(value.type)}' (the promise's inner type has no combined result union — await the promise arm after narrowing instead)`,
|
||||
);
|
||||
}
|
||||
if (inner.kind === "dyn") {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
expr,
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/union-await-promise-arm.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Awaiting a union whose promise arm settles a union
|
||||
// (`Promise<A | B> | undefined`): the promise arm is awaited, and a
|
||||
// non-promise arm answers its own value after one microtask turn.
|
||||
function maybe(flag: boolean): Promise<string | null> | undefined {
|
||||
if (!flag) return undefined;
|
||||
return Promise.resolve(flag ? "value" : null);
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const absent = await maybe(false);
|
||||
console.log(String(absent));
|
||||
const settled = await maybe(true);
|
||||
console.log(String(settled));
|
||||
const nil = await maybe(true);
|
||||
console.log(nil === null);
|
||||
|
||||
// Awaiting a non-promise arm takes exactly one microtask turn.
|
||||
const order: string[] = [];
|
||||
Promise.resolve().then(() => order.push("microtask"));
|
||||
const hopped = await maybe(false);
|
||||
order.push(`after:${String(hopped)}`);
|
||||
console.log(order.join(","));
|
||||
}
|
||||
main();
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3150, "san": 4400 }
|
||||
Reference in New Issue
Block a user