I'm not exactly sure what the intended use is for this, but it seems that we can set NoUnion
to always be never
if the input type is a union.
Some have pointed out that conditional types distribute over unions, which is referred to as distributive conditional types
Distributive conditional types occur when the checked type is a naked type parameter. These types are automatically distributed over union types during instantiation. For instance, if T extends U ? X : Y has an argument of A | B | C for T, it is resolved as (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y).
The key point here is 'naked type'; if the type is enclosed in a tuple type, the conditional type will no longer be distributive.
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
type NoUnion<Key> =
// If it's a basic type, UnionToIntersection<Key> remains the same, otherwise it creates an intersection of all types in the union and might not extend `Key`
[Key] extends [UnionToIntersection<Key>] ? Key : never;
type A = NoUnion<'a'|'b'>; // never
type B = NoUnion<'a'>; // a
type OtherUnion = NoUnion<string | number>; // never
type OtherType = NoUnion<number>; // number
type OtherBoolean = NoUnion<boolean>; // never since boolean is essentially true|false
In the last example, there's an issue because the compiler interprets boolean
as true|false
, resulting in NoUnion<boolean>
being never
. It may be solved by treating boolean
as a special case:
type NoUnion<Key> =
[Key] extends [boolean] ? boolean :
[Key] extends [UnionToIntersection<Key>] ? Key : never;
Note: The UnionToIntersection
function is sourced from here