mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Keep a returned arrow's own return type in a void slot
A value-returning arrow returned through a `() => void` interface member was adopted as void, so the enclosing function promised `() => number` while the lambda built a void closure: "a void result where the '() => number' slot promises ..." natively, though Node runs it (`repros/d2-void-slot-generic.ts`, pi `event-bus.ts:27`). A lambda that is the returned value of the function being lowered reaches that function's caller, so its own return type must survive the checker's void contextual type; the void adoption in lambdaSignature now skips such a lambda. Functions with a void result keep the existing discard semantics. tests/corpus/void-slot-returned-arrow.ts pins the shape both ways. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
97f4d092f1
commit
6014bb4ea5
@@ -1148,6 +1148,15 @@ export function lambdaSignature(
|
||||
// is the one the adoption block below takes for mappable inferred
|
||||
// returns, so this only changes programs that do not compile today.
|
||||
//
|
||||
// A lambda RETURNED from the function being lowered flows into that
|
||||
// function's result type, which OBSERVES its value — the checker's void
|
||||
// contextual type at such a site comes from an outer assignment target
|
||||
// that would discard the enclosing function's own result. So the void
|
||||
// adoption below must not fire there: `{ on: (h) => () => seen.push("y") }`
|
||||
// against a `(h) => void` field types the inner arrow `() => number`
|
||||
// (its own type) while its contextual type is the discarded `() => void`,
|
||||
// and the outer arrow's result is the `() => number` that must return it.
|
||||
const returnedValue = returnedIntoValueSlot(lowerer, node);
|
||||
// The contextual signature is looked up at most once per lambda: both
|
||||
// this decision and the adoption block below need it, and the checker
|
||||
// call is not free in a compile this hot.
|
||||
@@ -1165,6 +1174,7 @@ export function lambdaSignature(
|
||||
ts.isArrowFunction(node) && !ts.isBlock(node.body) && isStreamUndefCallExpr(lowerer, node.body)
|
||||
? VOID
|
||||
: (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) &&
|
||||
!returnedValue &&
|
||||
voidContextualSlot(lowerer, node, contextualSigs)
|
||||
? VOID
|
||||
: lowerer.declaredReturnType(node, node);
|
||||
@@ -1292,6 +1302,7 @@ export function lambdaSignature(
|
||||
if (
|
||||
!literalMethod &&
|
||||
!isAsyncLike &&
|
||||
!returnedValue &&
|
||||
!node.type &&
|
||||
!inferredLiteral &&
|
||||
ret.kind !== "void" &&
|
||||
@@ -1352,6 +1363,20 @@ function voidContextualSlot(
|
||||
);
|
||||
}
|
||||
|
||||
/** Whether `node` is the expression of a `return` in the function being
|
||||
* lowered, and that function's result is a function type promising a
|
||||
* value (`() => number`, not `() => void`). Such a lambda's value reaches
|
||||
* the caller of the enclosing function, so its own return type is kept
|
||||
* even when the checker's contextual type for it is `void` (see the ABI
|
||||
* decision in lambdaSignature). */
|
||||
function returnedIntoValueSlot(lowerer: Lowerer, node: ts.Node): boolean {
|
||||
let parent: ts.Node | undefined = node.parent;
|
||||
while (parent !== undefined && ts.isParenthesizedExpression(parent)) parent = parent.parent;
|
||||
if (parent === undefined || !ts.isReturnStatement(parent) || !parent.expression) return false;
|
||||
const result = lowerer.ctx.returnType;
|
||||
return result.kind === "func" && result.ret.kind !== "void";
|
||||
}
|
||||
|
||||
/** The type of the property an object-literal method fills on the
|
||||
* literal's contextual type; undefined when the literal has no contextual
|
||||
* type or the method has a computed name. */
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/void-slot-returned-arrow.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// A value-returning arrow RETURNED through a `() => void` interface member.
|
||||
// TypeScript types the inner arrow `() => number` (its own type) while the
|
||||
// checker contextually types it `() => void`; the enclosing arrow's result
|
||||
// is `() => number`, so the value is observed and must be produced. Node
|
||||
// runs this; the closure is called for its effect only.
|
||||
interface Subscriber {
|
||||
on(handler: (data: string) => void): () => void;
|
||||
}
|
||||
|
||||
function makeSubscriber(): Subscriber {
|
||||
const seen: string[] = [];
|
||||
return {
|
||||
on: (handler) => {
|
||||
seen.push("registered");
|
||||
handler("first");
|
||||
// Concise arrow whose body returns a number: the result is discarded
|
||||
// at the `() => void` slot but the arrow's own type keeps it.
|
||||
return () => seen.push("unsubscribed");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const off = makeSubscriber().on((data) => console.log("got", data));
|
||||
console.log("off is", typeof off);
|
||||
off();
|
||||
console.log("done");
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 1600, "san": 2400 }
|
||||
Reference in New Issue
Block a user