Here's a code example:
declare function test_ok<T>(arr: T[]): T;
test_ok([1, 2, "hello"]); // OK
test_ok([[1], [2], ["hello"]]); // OK
// note the nested array signature
declare function test_err<T>(arr: T[][]): T;
test_err([[1], [2], ["hello"]]); // ERR type "string" is not assignable to type "number"
test_err<string | number>([[1], [2], ["hello"]]); // OK if generic is specified
It seems that the general case, TypeScript is able to infer the best common type (a basic union) when given a heterogeneous array. However, if you try to scope the generic any further than just a simple array (such as the nested array above), it gives up. I've also found this with other cases (e.g. an array of functions where the generic is over the return type of the functions, rather than entire functions). Is this some sort of performance optimization?
I'm going to say this is more like intentional error-catching and not a performance optimization. When trying to infer T
given a set of values that are supposed to be of type T
, you can always succeed by widening T
to fit all values, but then it's impossible to catch a legitimate mistake, where one of the values was entered incorrectly. This is a judgment call by the designers of the language, I think, and likely any heuristic would produce some false positives and some false negatives.
The GitHub issue microsoft/TypeScript#31617 is a similar report, where a user expects string | number
to be inferred from two arguments, one of type string
and the other of type number
. The response from one of the language maintainers is:
This is an intentional trade-off so that generics can catch errors where you expect multiple objects to be of the same type, which is usually more common.
So, what can be done? Obviously you can manually specify T
as string | number
. Otherwise, if you want your function to be permissive and never throw an error, you can make T
the type of the given argument itself. The compiler is much more consistent about inferring a type given an value of that type than it is when given a value of some function of that type. So my workaround here would be this for arrays:
declare function test_fixed<T extends any[][]>(arr: T): T[number][number];
const ret = test_fixed([[1], [2], ["hello"]]); // string | number
In this case, T
is constrained to be a doubly-nested array, and the return type, T[number][number]
is the element type of the innermost array (if you take T
, and look up a value at a number
index, you will get another array type... if you look up a value of that at a number
index, you get the innermost element type... T[number][number]
.)
Okay, hope that helps. Good luck!
Link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With