Files
scriptc/tests/corpus/function-value-arity-completion.ts
T
Malte Ubl 5f169ed6fe Complete function-value arguments in optional calls
The `f?.()` branch of the optional-chain lowering forwarded a spread
argument list through completeFuncValueArgs but mapped a plain argument
list itself, so an omitted optional parameter was never padded with the
callee's undefined arm. The lib-boundary arity fence then refused the call
("2 arguments where its lowered signature takes 3"), which is what
`callbacks?.onRetryFinished?.(false, attempt)` hit: an unguarded call
completes its arguments, a guarded one did not.

Run both spellings through the same completion. It evaluates inside the
guard, so a nullish callee still evaluates no argument, and a typed rest
now packs from the guarded call too.

Corpus: optional-chained callbacks with a trailing optional parameter and
with a typed rest, through a present, absent, and undefined receiver.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f574073e2392ec24cd2cfca61651759d3359e603)
2026-10-28 19:49:28 -07:00

58 lines
2.0 KiB
TypeScript

// Calling a function value short is legal TypeScript whenever the omitted
// suffix is optional: the native callee still receives every parameter, so
// the call completes the missing slots with undefined exactly as the JS call
// would.
//
// The same holds for a call through an optional-chained property
// (`cb?.onRetryFinished?.(false, attempt)`), which evaluates the callee and
// its arguments once, in order.
interface RetryCallbacks {
onRetryFinished?: (success: boolean, attempt: number, finalError?: string) => void;
onAttempt?: (attempt: number, ...notes: string[]) => string;
}
function finish(callbacks: RetryCallbacks | undefined, ok: boolean, attempt: number): string[] {
const log: string[] = [];
const sink: RetryCallbacks = {
onRetryFinished: (success, n, finalError) => {
log.push(`finished ${success ? "ok" : "no"} ${n} ${finalError === undefined ? "none" : finalError}`);
},
onAttempt: (n, ...notes) => `${n}:${notes.join("+")}`,
};
sink.onRetryFinished?.(ok, attempt);
sink.onRetryFinished?.(ok, attempt, "boom");
callbacks?.onRetryFinished?.(ok, attempt + 1);
callbacks?.onAttempt?.(attempt, "a", "b");
return log;
}
console.log(finish(undefined, true, 1).join("; "));
console.log(
finish(
{
onRetryFinished: (success, attempt, finalError) => {
console.log(`callback ${success} ${attempt} ${finalError ?? "-"}`);
},
onAttempt: (attempt, ...notes) => {
console.log(`attempt ${attempt} ${notes.length}`);
return "ignored";
},
},
false,
2,
).join("; "),
);
// An omitted trailing argument for a callback whose parameter is optional in
// the middle of a longer signature.
type Reporter = (message: string, code?: number, detail?: string) => string;
function report(to: Reporter): void {
console.log(to("first"));
console.log(to("second", 42));
console.log(to("third", undefined, "why"));
}
report((message, code, detail) => `${message}/${code ?? 0}/${detail ?? "-"}`);