Reputation: 1025
I want not to have a second argument in function depending on the what first argument function returns. I can set that its type is undefined
but I still need to pass second argument. Sandbox
declare class Params<T> {
private params: T;
}
interface Data {
one: string;
two: {
three: string;
four: Params<{ more: string }>;
};
}
type MyFunction = <T>(
g: (d: Data) => T,
args: T extends Params<infer Args> ? Args : undefined
) => T extends Params<{}> ? string : T extends string ? T : unknown;
let mf = (() => {}) as MyFunction;
let a = mf(p => p.one); // expect not to have second arg
var b = mf(p => p.two); // b is unknown (correct now)
var b = mf(p => p.two.four,); // second arg has "more" property
Also there is an issue that in example b
I have a tooltip that I can pass second argument but when I do it TS shows error.
And when I provide the argument it shows the error
Upvotes: 0
Views: 2050
Reputation: 327934
If you want the function to take either one or two parameters depending on a generic type, you could use rest parameters as tuples to express this. Like this:
type MyFunction = <T>(
g: (d: Data) => T,
...args: T extends Params<infer Args> ? [Args] : []
) => T extends Params<{}> ? string : T extends string ? T : unknown;
That's similar to what you had, except that now args
is a rest parameter, and it's either one or zero elements long depending on what T
is. Now this works:
let mf = (() => {}) as MyFunction;
let a = mf(p => p.one);
var b = mf(p => p.two);
var c = mf((p: Data) => p.two.four, { more: "ads" });
But note that for c
I had to explicitly annotate p
as Data
. Without it, the compiler gets confused and infers any
, and then {more: "ads"}
is an error (because it couldn't tell that p.two.four
was of a Params<>
type.
What I'd probably do instead, though, is use overloads to distinguish the two quite different ways of calling this function:
type MyFunction = {
<A>(g: (d: Data) => Params<A>, args: A): string;
<T>(g: (d: Data) => T): T extends string ? T : unknown;
};
That's a lot less type manipulation (and cuts out most of your conditional types) and now the compiler really knows what to expect:
let mf = ((() => {}) as any) as MyFunction;
let a = mf(p => p.one);
var b = mf(p => p.two);
var c = mf(p => p.two.four, { more: "ads" });
All of that works perfectly. Okay, hope that helps. Good luck!
Upvotes: 2