I'm creating a wrapper to handle NextJS API requests in Typescript. However, I have a type error when I want to pass a single type for one of the generic types in the function.
I've created a simple function to show this error. The example below passes 2 parameters and changes parameter bar
to an array. foo
's type should be unknown
unless explicitly defined. bar
's type should always be inferred. When generic types are not defined explicitly, the function's return type is inferred correctly (line 6).
However, when generic type Foo
is set to number, a type error appears because type Bar
is automatically set as undefined and is not inferred from the parameter's type anymore (line 9).
Line 12 shows how you are forced to define all the generic types if you want to define at least 1 of them.
function test<Foo = unknown, Bar = undefined>(foo: Foo, bar: Bar) {
return { foo, bar: [bar] }
}
// Works - Return type { foo: number; bar: string[] }
const a = test(10, 'bar')
// Does not work - Return type { foo: number; bar: undefined[] }
const b = test<number>(10, 'bar')
// Works - Return type { foo: number; bar: string[] }
const c = test<number, string>(10, 'bar')
Is there a workaround to optionally define a generic type while not manually defining the others?
CodePudding user response:
TypeScript currently has no direct support for partial generic type argument inference. If you have a generic function with multiple type parameters, like declare function foo<T, U>(t: T, u: U): F<T, U>;
then you can either manually specify all of them like foo<X, Y>(x, y)
, or you can get the compiler to infer all of them like foo(x, y)
. You cannot specify some but get inference for the others, as in foo<X>(x, y)
. See microsoft/TypeScript#26242 for the relevant feature request.
And no, generic parameter defaults like declare function foo<T = A, U = B>(t: T, u: U): F<T, U>
do not give you this functionality. If there's a default, then specifying some of the parameters gives you the default for the ones you leave out. It does not infer anything. Maybe in the future there will be some syntax like declare function foo<T = infer, U = infer>(t: T, u: T): F<T, U>
which allows defaults to be pressed into service for partial type argument inference. But for now, it's just not part of the language.
Until and unless microsoft/TypeScript#26242 is implemented, there are workarounds. The most common one I use is currying. Since each generic function call requires you to specify all the type arguments or infer all the type arguments, you can emulate partial inference by splitting the single function call into multiple. So you can make const fooCurry: <T,>(t: T): <U,>(u: U) => F<T, U>
or the like.
For your example code, it looks like:
const testCurry = <F,>(foo: F) => <B,>(bar: B) => test(foo, bar);
And then the calls become
const b = testCurry<number>(10)('bar');
/* const b: { foo: number; bar: string[]; } */
Where you successfully specify that F
is number
but let the compiler infer that B
is string
.