Let's say I have the following discriminated unions and some associated types

type Union = 'a' | 'b'; type Product<A extends Union, B> = { f1: A, f2: B}; type ProductUnion = Product<'a', 0> | Product<'b', 1>;

Now I can take complements by using mapping types and Exclude

type UnionComplement = { [K in Union]: Exclude<Union, K> }; // {a: "b"; b: "a"} type UnionComplementComplement = { [K in Union]: Exclude<Union, Exclude<Union, K>> }; // {a: "a"; b: "b"}

So far all of this makes sense but things break down for ProductUnion when I try to take the double complement. The first complement works fine

type ProductComplement = { [K in Union]: Exclude<ProductUnion, { f1: K }> }; // {a: Product<'b', 1>; b: Product<'a', 0>}

The double complement is incorrect no matter what I try

type ProductComplementComplement = { [K in Union]: Exclude<ProductUnion, Exclude<ProductUnion, { f1: K }>> }; // {a: ProductUnion; b: ProductUnion}

I don't understand where the bug is because if I substitute the types then it should work. There are only 2 values for K when taking the double complement so let's try the first one

type First = Exclude<ProductUnion, Exclude<ProductUnion, { f1: 'a' }>>; // {f1: 'a'; f2: 0}

Second one also work

type Second = Exclude<ProductUnion, Exclude<ProductUnion, { f1: 'b' }>>; // {f1: 'b'; f2: 1}

All the constituent parts work but when combined in the mapping type it seems to break down. What am I missing here?

On a whim I tried adding a type parameter to see what would happen by abstracting the complementing process

type Complementor<T> = { [K in Union]: Exclude<T, { f1: K }> }; type DoubleComplementor<T> = { [K in Union]: Exclude<T, Exclude<T, { f1: K }>> };

Now if I apply the parametrized types to ProductUnion it works exactly as I expect