mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-06 08:23:19 +08:00
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>
47 lines
1.2 KiB
TypeScript
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));
|