Поигравшись с этим немного, я пришел к следующему. По-прежнему существует дублирование, но, по крайней мере, есть некоторая перекрестная проверка, которая может помочь защититься от ошибок. Основная проблема заключается в том, что он довольно многословен, и можно легко забыть одну комбинацию.
type Pair<A, B> = [A, B]
const pair = <A, B>(a: A, b: B): Pair<A, B> => [a, b]
type ShirtYellow = Pair<Formats.Shirt, Shirt.Yellow>
type ShirtOrange = Pair<Formats.Shirt, Shirt.Orange>
type FruitOrange = Pair<Formats.Fruit, Fruit.Orange>
type FruitLemon = Pair<Formats.Fruit, Fruit.Lemon>
const ShirtYellow: ShirtYellow = pair(Formats.Shirt, Shirt.Yellow)
const ShirtOrange: ShirtOrange = pair(Formats.Shirt, Shirt.Orange)
const FruitOrange: FruitOrange = pair(Formats.Fruit, Fruit.Orange)
const FruitLemon: FruitLemon = pair(Formats.Fruit, Fruit.Lemon)
export type Item = ShirtYellow | ShirtOrange | FruitOrange | FruitLemon
export const Item = { ShirtYellow, ShirtOrange, FruitOrange, FruitLemon };
Вот мои вторые попытки. На этот раз решение на основе объектов.
type AbstractItem<I extends { kind: Formats, type: any }> = I
export type ShirtItem = AbstractItem<{kind: Formats.Shirt, type: Shirt}>
export type FruitItem = AbstractItem<{kind: Formats.Fruit, type: Fruit}>
export type Item = AbstractItem<ShirtItem | FruitItem>
export const isShirt = (i: Item): i is ShirtItem => i.kind === Formats.Shirt
export const isFruit = (i: Item): i is FruitItem => i.kind === Formats.Fruit
export const getShirt = (i: Item): Shirt|null => isShirt(i) ? i.type : null
export const getFruit = (i: Item): Fruit|null => isFruit(i) ? i.type : null