typescript - Tomando complementos de uniones discriminadas
Digamos que tengo las siguientes uniones discriminadas y algunos tipos asociados
type Union = 'a' | 'b';
type Product<A extends Union, B> = { f1: A, f2: B};
type ProductUnion = Product<'a', 0> | Product<'b', 1>;
Ahora puedo tomar complementos usando tipos de mapeo y 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"}
Hasta ahora todo esto tiene sentido, pero las cosas se complican para ProductUnion cuando intento tomar el doble complemento. El primer complemento funciona bien
type ProductComplement = {
[K in Union]: Exclude<ProductUnion, { f1: K }>
};
// {a: Product<'b', 1>; b: Product<'a', 0>}
El doble complemento es incorrecto sin importar lo que intente
type ProductComplementComplement = {
[K in Union]: Exclude<ProductUnion, Exclude<ProductUnion, { f1: K }>>
};
// {a: ProductUnion; b: ProductUnion}
No entiendo dónde está el error porque si sustituyo los tipos entonces debería funcionar. Solo hay 2 valores para K al tomar el complemento doble, así que probemos con el primero
type First = Exclude<ProductUnion, Exclude<ProductUnion, { f1: 'a' }>>;
// {f1: 'a'; f2: 0}
El segundo también funciona
type Second = Exclude<ProductUnion, Exclude<ProductUnion, { f1: 'b' }>>;
// {f1: 'b'; f2: 1}
Todas las partes constituyentes funcionan, pero cuando se combinan en el tipo de mapeo parece fallar. ¿Qué me estoy perdiendo aquí?
Por capricho intenté agregar un parámetro de tipo para ver qué pasaría al abstraer el proceso complementario
type Complementor<T> = {
[K in Union]: Exclude<T, { f1: K }>
};
type DoubleComplementor<T> = {
[K in Union]: Exclude<T, Exclude<T, { f1: K }>>
};
Ahora, si aplico los tipos parametrizados a ProductUnion, funciona exactamente como espero
type Complement = Complementor<ProductUnion>;
// {a: Product<'b', 1>; b: Product<'a', 0>}
type DoubleComplement = DoubleComplementor<ProductUnion>;
// {a: Product<'a', 0>; b: Product<'b', 0>}
Respuesta #1
Esto fue de hecho un error: https://github.com/Microsoft/TypeScript/issues/28824. Gracias a Anders y su equipo, la próxima versión debería tener un comportamiento más consistente.
Respuesta #2
Los alias de tipo abstractos no se supone que funcionen exactamente de la misma manera que los en línea. Creo que ese es el punto.
EDITAR:
Está bien, parece un error.
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" }
