I have two literal types, and I want to exclude B from union A.
type A = 'a' | 'b'
type B = 'a'
When I do it like this:
type MyExclude<A2, B2> = A2 extends B2 ? never : A2
type C2 = MyExclude<A, B>
// C2 = 'b'
It works correctly, i.e. i get 'b'.
But when I do it directly like this:
type C = A extends B ? never : A
// C = 'a' | 'b'
It doesn't. Why should it matter whether I do it via a intermediary generic type or directly?
Thanks.
CodePudding user response:
When you do it as a generic, Typescript is going to apply the check distributively to each member of the union. So type C2 = MyExclude<A, B>
is going to be evaluated not as 'a | b' extends 'a' ? never : 'a | 'b'
, but rather ('a' extends 'a' ? never : 'a') | ('b' extends 'a' ? never : 'b')
. 'a' does extend B, so that results in never
. 'b' does not extend B, so that results in 'b'. And the final result is never | 'b'
, which is just 'b'
.
The distributive part does not happen if it's not a generic. type C = A extends B ? never : A
is just going to be evaluated once, as 'a | b' extends 'a' ? never : 'a | 'b'
. 'a' | 'b'
does not extend 'a'
, so the result is the whole type, 'a' | 'b'
See distributive conditional types
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 argumentA | B | C
forT
is resolved as(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
.