mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-02 07:43:42 +08:00
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)
58 lines
2.0 KiB
TypeScript
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 ?? "-"}`);
|