I'm new to conditional types, so I tried the most obvious static way, no success:
type NoUnion<Key> =
Key extends 'a' ? 'a' :
Key extends 'b' ? 'b' :
never;
type B = NoUnion<'a'|'b'>;
The B type is still a union. Would somebody please school me?
Here's a playground.
I am unsure what the usecase for this is, but we can force the NoUnion
to never
if the passed type is a union type.
As other mentioned conditional types distribute over a union, this is called distributive conditional types
Conditional types in which the checked type is a naked type parameter are called distributive conditional types. Distributive conditional types are automatically distributed over union types during instantiation. For example, an instantiation of T extends U ? X : Y with the type argument A | B | C for T is resolved as (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y).
The key there is 'naked type', if we wrap the type in a tuple type for example 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 this is a simple type UnionToIntersection<Key> will be the same type, otherwise it will an intersection of all types in the union and probably will 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 just true|false
The last example is an issue, since boolean
is seen by the compiler as true|false
, NoUnion<boolean>
will actually be never
. Without more details of what exactly you are trying to achieve it is difficult to know if this is a deal breaker, but it could be solved by treating boolean
as a special case:
type NoUnion<Key> =
[Key] extends [boolean] ? boolean :
[Key] extends [UnionToIntersection<Key>] ? Key : never;
Note: UnionToIntersection
is taken from here
By the way, the "simpler" one I was trying to come up with looks like this:
( NOTE: the following doesn't work in TS after v3.3, due to microsoft/TypeScript#34504:
// type NotAUnion<T> = [T] extends [infer U] ?
// U extends any ? [T] extends [U] ? T : never : never : never;
instead one can use the following since defaults still get instantiated before distribution, at least for now: )
type NotAUnion<T, U = T> =
U extends any ? [T] extends [U] ? T : never : never;
This should work (please test it; not sure why I got the original version in my answer to another question wrong but it's fixed now ). It's a similar idea to the UnionToIntersection
: you want to make sure that a type T
is assignable to each part of T
if you distribute it. In general that's only true if T
is a union with just one constituent part (which is also called "not a union").
Anyway, @TitianCernicovaDragomir's answer is perfectly fine also. Just wanted to get this version out there. Cheers.
This also works:
type NoUnion<T, U = T> = T extends U ? [U] extends [T] ? T : never : never;
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