The issue arises from the mismatch in return types between the default getter function, which returns Whatever
, and the requirement of the doSomething
declaration that the getter must return T
. Since T
is a generic type parameter that can be any type, there is no guarantee that Whatever
will be compatible with T
. The TypeScript compiler does not recognize that when a default value is provided, T
is not mandatory, and the actual return type of doSomething
should be Whatever[]
. One way to address this is by using overload declarations for doSomething
:
function doSomething<T>(values: Whatever[], getter: (whatever: Whatever) => T): T[];
function doSomething(values: Whatever[]): Whatever[];
// Ensure compatibility with both variants
function doSomething<T>(values: Whatever[], getter: (whatever: Whatever) => T | Whatever = val => val ): (T | Whatever)[] {
return values.map(value => getter(value));
}
Update to clarify the question:
I aim to avoid returning "Whatever | T" as it would require checking the response type every time I use this function.
When calling this function, TypeScript considers only the two overloaded signatures and does not utilize the implementation signature during overload resolution at call sites of doSomething()
. In fact, the implementation return type could be simply declared as any
, similar to what's demonstrated in the documentation examples for overloads - primarily used for ensuring type-checking within the implementation scope where stricter types might not offer significant advantages.
I wish to write code that infers the return type from the getter function and uses it as T.
If you omit the generic argument when invoking doSomething
, the compiler will deduce T
from the return type of the getter
function. You can achieve this behavior with the following example:
interface Whatever { w: string };
function doSomething<T>(values: Whatever[], getter: (whatever: Whatever) => T): T[];
function doSomething(values: Whatever[]): Whatever[];
function doSomething<T>(values: Whatever[], getter: (whatever: Whatever) => T | Whatever = val => val ): (T | Whatever)[] {
return values.map(value => getter(value));
}
function example() {
const getter1 = (whatever: Whatever) => whatever.w; // returns string
const getter2 = (whatever: Whatever) => whatever.w.length; // returns number
const values: Whatever[] = [];
const r0 = doSomething(values); // const r1: Whatever[]
const r1 = doSomething(values, getter1); // const r1: string[]
const r2 = doSomething(values, getter2); // const r2: number[]
}