Files
scriptc/tests/corpus/function-value-declared-arity.ts
T
Malte Ubl 1846753a3c Pin declared-arity narrowing as a loud refusal, and the accepted shapes
A function value's declared type may name fewer parameters than the
function it holds, and a later widening call then passes arguments the
declared type never spelled. JavaScript's function object carries all of
its parameters, so nothing may be dropped on that path — that would be a
silent divergence from Node.

The narrowed store is already a named refusal (a compiled function value is
one concrete native ABI: "'(string, number) => number' values where
'(string) => number' is expected"), reported at the narrowing site for a
binding and for an object-literal member slot. Record both in the
diagnostics snapshot so the refusal cannot regress into a silent drop.

Pin the shapes that DO keep every argument: a class member whose overloads
spell fewer parameters than its implementation, the member read as a value
(the read keeps the implementation's JS arity), and a value that entered
through an untyped slot and is widened back.

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

32 lines
1.3 KiB
TypeScript

// A function holds ONE implementation, and a declared type only constrains
// call sites. An overloaded member's implementation may declare more
// parameters than any single overload spells; every overload's arguments
// still reach it — and so do the arguments of a later WIDENED call, because
// the value read out of the member keeps the implementation's full arity
// (JavaScript has no arity check; a missing argument is just undefined).
class Writer {
write(text: string): number;
write(text: string, times: number): number;
write(text: string, times?: number): number {
return text.length * (times === undefined ? 1 : times);
}
}
const writer = new Writer();
console.log(writer.write("abc"));
console.log(writer.write("abcd", 3));
// The member read as a value carries the same one implementation, so the
// two-argument call reaches its second parameter.
const writeValue = writer.write;
console.log(writeValue("abc"));
console.log(writeValue("abcd", 3));
// A value that entered through an untyped slot keeps the arity it was
// created with; widening it back delivers every argument.
const loose: any = (text: string, times: number) => text.length * times;
const widened = loose as (text: string, times: number) => number;
console.log(widened("abcd", 3));
console.log(widened("xy", 10));