TypeScript doesn't really have direct support for an "exhaustive array". You can guide the compiler into checking this, but it might be a bit messy for you. A stumbling block is the absence of partial type parameter inference (as requested in microsoft/TypeScript#26242). Here is my solution:
type Furniture = 'chair' | 'table' | 'lamp' | 'ottoman';
type AtLeastOne<T> = [T, ...T[]];
const exhaustiveStringTuple = <T extends string>() =>
<L extends AtLeastOne<T>>(
...x: L extends any ? (
Exclude<T, L[number]> extends never ?
L :
Exclude<T, L[number]>[]
) : never
) => x;
const missingFurniture = exhaustiveStringTuple<Furniture>()('chair', 'table', 'lamp');
// error, Argument of type '"chair"' is not assignable to parameter of type '"ottoman"'
const extraFurniture = exhaustiveStringTuple<Furniture>()(
'chair', 'table', 'lamp', 'ottoman', 'bidet');
// error, "bidet" is not assignable to a parameter of type 'Furniture'
const furniture = exhaustiveStringTuple<Furniture>()('chair', 'table', 'lamp', 'ottoman');
// okay
As you can see, exhaustiveStringTuple
is a curried function, whose sole purpose is to take a manually specified type parameter T
and then return a new function which takes arguments whose types are constrained by T
but inferred by the call. (The currying could be eliminated if we had proper partial type parameter inference.) In your case, T
will be specified as Furniture
. If all you care about is exhaustiveStringTuple<Furniture>()
, then you can use that instead:
const furnitureTuple =
<L extends AtLeastOne<Furniture>>(
...x: L extends any ? (
Exclude<Furniture, L[number]> extends never ? L : Exclude<Furniture, L[number]>[]
) : never
) => x;
Playground link to code
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…