I've created a generic function type with a return parameter which is entirely dependent on the input parameter types, and the generic parameter has an extends
clause. Here's a dummy version:
type Foo = {}
type MyFunc <T extends Foo> = (a: T, b: T) => T[];
const bar: MyFunc<number> = (a: number, b: number) => [a,b];
const baz: MyFunc<boolean> = (a: boolean, b: boolean) => [a,b];
I'm using the generic parameter to ensure 1) a
and b
extend Foo
and 2) a
and b
are the same type.
The problem I'm running into is with taking a MyFunc
as a parameter type. I don't need an exact type for T
; I only need to ensure the parameter constraints are satisfied when it is called. For example, I'd like to do the following:
const makeArray = (qux: MyFunc) => qux(1,2);
1
and 2
are of the same type and both extend Foo
, so I know the function call will work and I'll get back a number[]
. However, the above doesn't compile. TypeScript says:
Generic type 'MyFunc' requires 1 type argument(s)
If I use (qux: MyFunc<any>)
, then no type-checking at all occurs.
Is there any other syntax I can use to tell TypeScript to check the input parameters but not to require specifying the exact type ahead of time?
CodePudding user response:
It looks like you've got the scope of the generic type parameter wrong. There are two distinct (but related) "flavors" of generics in TypeScript. See TypeScript how to create a generic type alias for a generic function? and typescript difference between placement of generics arguments for example.
There are generic types, where the type parameter is declared on the type name itself, like
// type GenType<T> = ...T...
And there are generic functions, where the type parameter is declared on the call signature, like
// type GenFunc = <T>(...T...)=>...
You originally had MyFunc
as a generic type that evaluates to a non-generic function
// original
type MyFunc<T extends Foo> = (a: T, b: T) => T[];
but that doesn't seem to be what you want. Instead, you should make it a non-generic type that evaluates to a generic function:
// changed
type MyFunc = <T extends Foo>(a: T, b: T) => T[];
So now you can make a function of type MyFunc
and it will act like your old MyFunc<T>
for every possible T
:
// actually generic
const foo: MyFunc = (a, b) => [a, b];
If you really want to restrict the function so that it takes some specific type argument, you can use instantiation expressions:
const bar = foo<number>;
// const bar: (a: number, b: number) => number[]
const baz = foo<boolean>;
// const baz: (a: boolean, b: boolean) => boolean[]
But I don't know how useful that is.
Anyway, now you can implement makeArray
how you want it, where MyFunc
does not require a type argument:
const makeArray = (qux: MyFunc) => qux(1, 2);
// const makeArray: (qux: MyFunc) => number[]
And look, the compiler infers that the return type of makeArray
is number[]
, as desired!