When creating a function with a generic function parameter, is there a way to prevent bad utilization of the parameter?
The perceived standard case types the function as "any function whatsoever" inside:
const rip = <T extends (...args: any[]) => any>(f: T) => f('invalid');
rip((a: number) => a.toFixed(2)); // oops
Given any
is a boo-doo and makes everything bivariant, i tried the following, but it still doesn't account for number of parameters:
const rip2 = <T extends (...args: readonly never[]) => unknown>(f: T) => f();
rip2((a: number) => a.toFixed(2)); // oops again
This has likely been asked before, but searching for it proved difficult for me (too many similar topics around "generic" and "higher order").
To avoid XY-problems, the origin of the problem is writing a function, which wraps functions with a check. Here is how i first intended to write it:
declare const check: () => boolean;
const checked = <T extends (...args: any[]) => any>(f: T): (...args: Parameters<T>) =>
ReturnType<T> | undefined => (...args) => {
if (check()) return;
return f(...args); // `f('invalid')` would not cause an error
};
const g = checked((a: number) => a.toFixed(2));
It suffers from the same issue. Inside, the function is (...args: any[]) => any
, and therefore has no checks. Similar to above, a more strict constraint doesn't solve the issue entirely (no parameters still works), readonly
causes an error, and a cast is necessary:
declare const check: () => boolean;
const checked = <T extends (...args: /* readonly */ never[]) => unknown>(f: T): (...args:
Parameters<T>) => ReturnType<T> | undefined => (...args) => {
if (check()) return;
return f(...args) as ReturnType<T>; // `f()` would not cause an error
};
const g = checked((a: number) => a.toFixed(2));
CodePudding user response:
By being more specific with types (i.e. creating a separate generic type for args and return-type, rather than inferring them from a single function type), TypeScript is better able to deduce that something's wrong if the function is supplied incorrect parameters.
declare const check: () => boolean;
const checked = <A extends any[], R>(f: (...args: A) => R): (...args: A) =>
R | undefined => (...args) => {
if (check()) return;
return f(...args);
};
const g = checked((a: number) => a.toFixed(2));