I’m using distributive conditional types to produce a union type from a union type. I’m transforming each union member into an object type, and only some elements of the original union correspond to this object type; I want to exclude the others from the union.
It looks something like this:
type ExtractFoo<T> = T extends { a: infer A; b: infer B } ? [A, B] : never;
type ExtractBar<T> = T extends { c: infer C; d: infer D } ? [C, D] : never;
type Transform<T> = T extends unknown ? { foo: ExtractFoo<T>; bar: ExtractBar<T> } : never;
I know that TypeScript automatically removes never
from union types, so I was surprised when I saw that TypeScript fails to simplify the output of this invocation of Transform
:
type T = Transform<{ a: 1; b: 2; c: 3; d: 4 } | { a: 'a'; b: 'b' } | { a: true; c: false }>
// Expected: { foo: [1, 2]; bar: [3, 4] }
// Actual: type T = {
// foo: [1, 2];
// bar: [3, 4];
// } | {
// foo: ["a", "b"];
// bar: never;
// } | {
// foo: never;
// bar: never;
// }
My understanding is that an object type with a field of type never
is exactly equivalent to never
, as both are uninhabited. As such, I was surprised that TypeScript doesn’t simplify { foo: ["a", "b"]; bar: never }
out of the union the same way it does with other ways of phrasing the never
type.
Digging into it further, I found that surprisingly, TypeScript always treats { foo: never }
as different from never
, even though they are the exact same type:
(x: { foo: never }): never => x;
// Error: Type '{ foo: never; }' is not assignable to type 'never'.
Why does TypeScript treat { foo: never }
as different from never
?