Why is TS not complaining here:
function print<G>(params: G[]): G {
return params[0];
}
console.log(print(["1",false,"2",5]))
Array elements have different types no?
CodePudding user response:
TypeScript wouldn't complain because you're using generics correctly for its intended purpose, called type argument inference:
Here we use type argument inference — that is, we want the compiler to set the value of Type for us automatically based on the type of the argument we pass in:
let output = identity("myString");
Notice that we didn’t have to explicitly pass the type in the angle brackets (
<>
); the compiler just looked at the value "myString" [...]. While type argument inference can be a helpful tool to keep code shorter and more readable [...]
In this case, the inferred type of G
will be based on the type of the array you are passing to the function.
["1", false, "2", 5]
will have an inferred type of Array<string | boolean | number>
, so G
will have the inferred union type of string | boolean | number
. And that is exactly what params[0]
is returning, which matches the explicit return type you've defined.
CodePudding user response:
We'll be using IsUnion
from this answer, along with UnionToIntersection
from here. Those answers already have excellent explanations, so if you're interested, make sure to read them.
The only magic I do here is using IsUnion
to check if G
is a union:
function print<G>(params: readonly (IsUnion<G> extends true ? never : G)[]): G {
return params[0];
}
I use readonly
so you can pass readonly and regular arrays (since regular arrays are assignable to readonly arrays, but not the other way around). Then there is IsUnion<G> extends true ? never : G
.
TypeScript is actually able to infer the type of G
here, so it can pass it to IsUnion
. If G
was a union type, we disallow that and replace it with never
.
Here's a playground with some cases. Note that this will error for types like 1 | 2 | 3
since that's still a union.
CodePudding user response:
What @Terry noted is correct. TypeScript's Heuristics will infer G
to be a union type of all the elements in the array. If you don't want this behaviour, this might be helpful:
function print2<G>(params: [G, ...(G & {})[]]): G {
return params[0];
}
print2(["1","2"]) // Valid
print2(["1",false,"2",5]) // Error
Here, G
will be inferred as the type of the first element in the tuple. The rest of the elements must comply to this type.
We give params
a tuple type. The first element of this tuple is just G
and TypeScript can use this element to infer G
. The rest of the tuple... well it's an potentially unlimited amount G
s too, but we intersect them with {}
. This is a common way of telling TypeScript "Hey, please just take the type of G
here, but don't use this position to infer it". You can see a discussion about that here.