取受歧视工会的补充

Taking complements of discriminated unions

假设我有以下可区分的联合和一些关联类型

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

现在我可以使用映射类型和 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"}

到目前为止,所有这些都是有道理的,但是当我尝试采用双补码时,ProductUnion 的事情就崩溃了。第一个补充工作正常

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

无论我怎样尝试,双重补码都不正确

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

我不明白错误在哪里,因为如果我替换类型,它应该可以工作。取双补时 K 只有 2 个值,所以让我们尝试第一个

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

第二个也可以

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

所有组成部分都有效,但在映射类型中组合时似乎会崩溃。我在这里错过了什么?

一时兴起,我尝试添加一个类型参数,看看通过抽象补充过程会发生什么

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

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

现在,如果我将参数化类型应用到 ProductUnion,它会完全按照我的预期工作

type Complement = Complementor<ProductUnion>;
// {a: Product<'b', 1>; b: Product<'a', 0>}

type DoubleComplement = DoubleComplementor<ProductUnion>;
// {a: Product<'a', 0>; b: Product<'b', 0>}

抽象类型别名的行为方式不应与内联别名完全相同。我认为这就是重点。

编辑:

好的,它看起来确实像一个错误。

type E1 = Exclude<{ f1: 'a' } | { f1: 'b' },
            Exclude<{ f1: 'a' } | { f1: 'b' }, { f1: 'a' }>>;

// E1 = { f1: "a" }

type E2<K> = Exclude<{ f1: 'a' } | { f1: 'b' },
               Exclude<{ f1: 'a' } | { f1: 'b' }, { f1: K }>>;

// E2<K> = { f1: "a" } | { f1: "b" }
//         ^ no `K` in the resulting type
//         the compiler has somehow eliminated `K` from the resulting type

// no matter what you do from here on, doesn't get re-evaluated with K in it.
//
type E2a = E2<'a'>;
// E2a = { f1: "a" } | { f1: "b" }

type E2b = E2<'b'>;
// E2b = { f1: "a" } | { f1: "b" }

这确实是一个错误:https://github.com/Microsoft/TypeScript/issues/28824。感谢 Anders 和团队,下一个版本应该有更一致的行为。