Lets split our tast into several smaller:
First of all we need to iterate over each tuple and check if both elements have same type.
If both element share same type, such tuple should be infered as string[]
or number[]
, otherwise it should be infered as (string|number)[]
. So we need some util to check if type is union or not.
// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
// credits https://stackoverflow.com/users/125734/titian-cernicova-dragomir
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;
Now, when we have such util, we should iterate through infered type of our argument and check if each tuple meet our requirement. Smth similar to Array.prototype.map
:
type MapPredicate<T> = T extends Array<infer Elem> ? IsUnion<Elem> extends true ? never : T : never
type IsEveryValid<
Arr extends Array<unknown>,
Result extends Array<unknown> = []
> = Arr extends []
? []
: Arr extends [infer H]
? [...Result, MapPredicate<H>]
: Arr extends [infer Head, ...infer Tail]
? IsEveryValid<[...Tail], [...Result, MapPredicate<Head>]>
: Readonly<Result>;
// [number[], string[], (() => void)[], never, never]
type Result = IsEveryValid<[number[], string[], (() => void)[], (string | number)[], (string | (() => number))[]]>
As you see, I just replaced all invalid tuples with never
.
Lets write out function definition:
const fn = <T, Tuple extends T[]>(...tuples: [...Tuple]) => [...tuples];
But wait, how we can apply our util type to infered generics?
Intersection comes to help!
const fn = <T, Tuples extends T[], IsValid extends IsEveryValid<[...Tuples]>>(...tuples: [...Tuples] & IsValid) => [...tuples];
fn(
[1, 2], // valid
["a", "b"], // valid
[() => { }, () => { }], // valid
[1, "2"], // invalid,
[() => 1, "foo"] // invalid
)
Playground
Pls, keep in mind, if you provide even one invalid tuple, TS will highlight all of them, because it treats it as one argument.
More information about iteration over the tuples you can find in my blog