Closed

Description
TypeScript Version:
$ tsc --version
Version 3.3.0-dev.20181122
Search Terms:
unexpected, exclude
Code
/*
Some basic definitions of unions
*/
type Union = 'a' | 'b';
type Product<A extends Union, B> = { f1: A, f2: B};
type ProductUnion = Product<'a', 0> | Product<'b', 1>;
/*
These work as I would expect. Each element in the union is mapped
the complement or the double complement with nested 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"}
/*
This also works as I would expect
*/
type ProductComplement = {
[K in Union]: Exclude<ProductUnion, { f1: K }>
};
// {a: Product<'b', 1>; b: Product<'a', 0>}
/*
Double complement on the other hand doesn't work
*/
type ProductComplementComplement = {
[K in Union]: Exclude<ProductUnion, Exclude<ProductUnion, { f1: K }>>
};
// {a: ProductUnion; b: ProductUnion}
/*
Explicit inlining works as I would expect
*/
type First = Exclude<ProductUnion, Exclude<ProductUnion, { f1: 'a' }>>;
// {f1: 'a'; f2: 0}
type Second = Exclude<ProductUnion, Exclude<ProductUnion, { f1: 'b' }>>;
// {f1: 'b'; f2: 1}
/*
Making parametrized types works as I would expect
*/
type Complementor<T> = {
[K in Union]: Exclude<T, { f1: K }>
};
type DoubleComplementor<T> = {
[K in Union]: Exclude<T, Exclude<T, { f1: K }>>
};
type Complement = Complementor<ProductUnion>;
// {a: Product<'b', 1>; b: Product<'a', 0>}
type DoubleComplement = DoubleComplementor<ProductUnion>;
// {a: Product<'a', 0>; b: Product<'b', 0>}
Expected behavior:
I would expect the parametrized and non-parametrized type to work the same way but that's not
the case.
Actual behavior:
See the comments.
Playground Link: link
Related Issues: Some issues related to Pick
and Exclude
but didn't seem to apply.