Если вы включили --strictNullChecks
, думаю, я бы сделал это следующим образом:
type Fun = (...args: any[]) => any;
type FunFrom<F, G> = F extends Fun ? F : G extends Fun ? G : () => {};
type IfFun<F, T> = F extends Fun ? T : never;
type Ret<T> = T extends (...args: any[]) => infer R ? R : never
declare function functionPair<
F extends Fun | undefined = undefined,
G extends ((...args: (F extends Fun ? Parameters<F> : any[])) => any)
| undefined = undefined
>(
f?: F,
g?: G
): (...args: Parameters<FunFrom<F, G>>) => {
[K in IfFun<F, 'f'> | IfFun<G, 'g'>]: K extends 'f' ? Ret<F> : Ret<G>
};
Это довольно уродливо, но дает вам поведение, которое вы ищете:
const a = functionPair(); // () => {}, as expected
const b = functionPair((foo: string) => foo); // (foo: string) => { f: string; }, as expected
const c = functionPair((foo: string) => foo, (bar: number) => bar); // Error, incompatible signatures, as expected
const d = functionPair((foo: string) => foo, (bar: string) => bar.length); // (foo: string) => { f: string; g: number; }, as expected
const e = functionPair(undefined, undefined); // () => {}, as expected
const f = functionPair(undefined, (bar: string) => bar.length); // (bar: string) => { g: number; }, as expected
Я решил использовать только два параметра типа F
и G
и вместо A
использовать Parameters<FunFrom<F, G>>
.Обратите внимание, что Parameters
- это встроенная функция типа, аналогичная вашей ArgumentTypes
.
Кроме того, для возвращаемого типа возвращаемой функции я делаю несколько уродливый отображенный тип.Сначала я планировал сделать что-то вроде IfFun<F, {f: Ret<F>}> & IfFun<G, {g: Ret<G>}>
, что (я считаю) более понятно, но результирующий тип {f: X, g: Y}
лучше, чем пересечение {f: X} & {g: Y}
.
В любом случае, надеюсь, это поможет.Удачи!
Если вы хотите иметь возможность отключить --strictNullChecks
, то определения становятся еще более привлекательными:
type Fun = (...args: any[]) => any;
type AsFun<F> = [F] extends [Fun] ? F : never
type FunFrom<F, G> = AsFun<IfFun<F, F, IfFun<G, G, () => {}>>>;
type IfFun<F, Y, N=never> = F extends undefined ? N :
0 extends (1 & F) ? N : F extends Fun ? Y : N;
type Ret<T> = T extends (...args: any[]) => infer R ? R : never
declare function functionPair<
F extends Fun | undefined = undefined,
G extends ((...args: IfFun<F, Parameters<F>, any[]>) => any)
| undefined = undefined
>(
f?: F,
g?: G
): (...args: Parameters<FunFrom<F, G>>) => {
[K in IfFun<F, 'f'> | IfFun<G, 'g'>]: K extends 'f' ? Ret<F> : Ret<G>
};
Разница в том, что IfFun<>
необходимоуметь отличать функции от undefined
и any
, которые появляются в неудачных местах при выключении --strictNullChecks
.Это потому, что undefined extends Function ? true : false
начинает возвращать true
, а any
начинает выводиться, когда вы передаете ручное значение undefined
в функции.Отличить undefined
довольно просто, поскольку Function extends undefined ? true : false
все еще false
, но различение any
раздражает и включает в себя забавный бизнес .
Удачи снова!