Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 33 additions & 9 deletions packages/compiler/src/frontend/lowering/lower-calls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6081,7 +6081,17 @@ export function lowerPromiseMethodCall(L: Lowerer, call: ts.CallExpression,
// contextual type spelled (a module-namespace type has no mapping —
// the handle is the value's only story, isIslandExpr's local rule).
if (inner.kind === "jsval") markJsvalHandlerParams(L, call.arguments[0]!);
let cb = L.lowerExpr(call.arguments[0]!);
// When the settled value is JSVAL, the callback body should allow JSVAL
// to flow into any slot without shape checks — the actual runtime value
// is an opaque island handle, not the TypeScript type parameter.
const prevInJsvalThenHandler = L.inJsvalThenHandler;
if (inner.kind === "jsval") L.inJsvalThenHandler = true;
Comment thread
vercel[bot] marked this conversation as resolved.
let cb: IrExpr;
try {
cb = L.lowerExpr(call.arguments[0]!);
} finally {
L.inJsvalThenHandler = prevInJsvalThenHandler;
}
// A TYPED handler on a DYN-settling promise (the tracePromise
// result's `.then((value) => ...)` — the checker's generic
// instantiation typed the parameter, but the settled value is a
Expand Down Expand Up @@ -6183,20 +6193,34 @@ export function lowerPromiseMethodCall(L: Lowerer, call: ts.CallExpression,
);
}
const param = cb.type.params[0];
if (param !== undefined && !typeEquals(param, inner)) {
// When the settled value is JSVAL, the handler's parameter type comes from
// the TypeScript checker's view of the promise type parameter — which may
// not match JSVAL. Skip the parameter type check in this case; the actual
// runtime value is an island handle regardless of the declared type.
if (inner.kind !== "jsval" && param !== undefined && !typeEquals(param, inner)) {
L.unsupported(
"SC1090",
call.arguments[0]!,
`then handlers whose parameter is not the settled value's type (expected '${L.fmt(inner)}', got '${L.fmt(param)}')`,
);
}
const resultT = L.mapTypeOf(L.typeOf(call));
if (resultT?.kind !== "promise") {
L.noLowering(
"then with this handler's result type",
call,
"the combined result must be a representable promise",
);
// When the settled value is JSVAL, the result promise type cannot be
// inferred from the TypeScript checker (the checker's type parameter
// reflects the declared module namespace, not the actual runtime {}).
// Force Promise<JSVAL> so the result type matches the actual value.
let resultT: IrType & { kind: "promise" };
if (inner.kind === "jsval") {
resultT = { kind: "promise", inner: JSVAL };
} else {
const mapped = L.mapTypeOf(L.typeOf(call));
if (mapped?.kind !== "promise") {
L.noLowering(
"then with this handler's result type",
call,
"the combined result must be a representable promise",
);
}
resultT = mapped;
}
const R = resultT.inner;
const fnName = `%fn${L.lambdaCounter++}_then`;
Expand Down
12 changes: 12 additions & 0 deletions packages/compiler/src/frontend/lowering/lowerer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1415,6 +1415,13 @@ export class Lowerer {
readonly liftedFns: IrFunction[] = [];
lambdaCounter = 0;

/** True while compiling the callback argument of `.then()` on a promise
* whose settled type is JSVAL. Suppresses `requireExactShape` in
* `coerceInto` — the settled value is an opaque island handle, so any
* value flowing out of the callback may also be JSVAL regardless of the
* TypeScript checker's static view of the promise's type parameter. */
inJsvalThenHandler = false;

/** Statement lists currently mid-lowering, innermost last: the forward-
* capture machinery (predeclareForwardCapture) needs to know which later
* statements of an OPEN list a symbol's declaration sits in, which scope
Expand Down Expand Up @@ -6542,6 +6549,11 @@ export class Lowerer {
}
return e;
}
// Inside a .then() callback on a JSVAL-settling promise, the settled value
// is an opaque island handle — the TypeScript checker's view of the type
// parameter is not trustworthy (the actual runtime value may be {}). Allow
// any value to flow through without a shape check.
if (this.inJsvalThenHandler) return e;
this.requireExactShape(node, e.type, expected);
return e;
}
Expand Down