Files
scriptc/tests/corpus/6382-variadic-wrapper-methods-this.ts
T
Malte UblandClaude Opus 5.5 cfb50d16e2 Specialize variadic generic wrappers to the wrapped function's signature
Higher-order wrappers over `(...args: any) => any` that forward `arguments`
with `fn.apply(this, arguments)` and type their options with
`Parameters<F>` / `ReturnType<F>`, and their rest-tuple rewrite, did not
compile. Generic calls already monomorphize per call site; these pieces now
resolve to that site's concrete signature:

- Rest parameters typed as fixed tuples (`...args: [A, B]`, `...args: A`
  bound to a tuple, `...args: Parameters<F>`) flatten to positional
  parameters, so `(...args: [A, B]) => R` and `(a: A, b: B) => R` share one
  function type. Trailing optional positions become `T | undefined` slots.
- `Parameters<F>`, `ReturnType<F>`, `Awaited<ReturnType<F>>` and the
  `ReturnType<F> extends PromiseLike<infer U> ? U : …` spelling resolve
  through F's binding.
- A parameterless function expression reading `arguments` lowers against
  the fixed signature its value is asserted to (or contextually typed as);
  `arguments` is that signature's tuple.
- `f.apply(thisArg, args)` and `f(...args)` over a forwarded tuple call
  directly, passing `thisArg` as the receiver. A tuple that is only
  forwarded is never allocated.
- `fn.name` on a function parameter folds to the call site's static name;
  the instantiation key carries it.

Also: optional callback fields narrowed at the call (`o.cb ? o.cb(x) : …`)
compile, effect-free surplus arguments to fixed-arity function values are
dropped, and `arguments.length` no longer counts a `this` parameter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:03:44 -07:00

47 lines
1.2 KiB
TypeScript

// Variadic generic wrappers on methods: the wrapper passes its own `this`
// through `fn.apply(this, arguments)`, so a wrapped `function (this: C)`
// stored as a class property sees the receiver it was called on.
type AnyFn = (...args: any) => any;
function logged<F extends AnyFn>(fn: F, label: string): F {
return function (this: unknown) {
console.log(`[${label}] call with ${arguments.length} args`);
return fn.apply(this, arguments as any);
} as F;
}
class Counter {
count = 0;
step: number;
constructor(step: number) {
this.step = step;
}
bump = logged(function (this: Counter, times: number): number {
this.count += this.step * times;
return this.count;
}, "bump");
reset = logged((): void => {
this.count = 0;
}, "reset");
}
const c = new Counter(5);
console.log(c.bump(1));
console.log(c.bump(2));
c.reset();
console.log(c.count);
class Repo {
base: string;
constructor(base: string) {
this.base = base;
}
load = logged(async function (this: Repo, id: string): Promise<string> {
await Promise.resolve();
return `${this.base}/${id}`;
}, "load");
}
const repo = new Repo("https://x.test");
repo.load("7").then((url) => console.log("loaded", url));