Home > Net >  TS not complaining about function parameter which is generic array and has different type of element
TS not complaining about function parameter which is generic array and has different type of element

Time:09-30

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 Gs 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.


Playground

  • Related