type-fest
type-fest copied to clipboard
`LiteralList<Union>`
I want to be able to define an array that contains exactly the elements of all possible values of a union once.
type Union = "prevStep" | "nextStep" | "submit" | "order";
// this works:
const list: LiteralList<Union> = ["nextStep", "order", "prevStep", "submit"];
// this fails:
const list: LiteralList<Union> = ["nextStep", "order", "prevStep", "submit", "submit"];
// this fails:
const list: LiteralList<Union> = ["nextStep", "order", "prevStep"];
I already made this work with this type:
type LiteralList<T extends string, U = T> = [T] extends [never]
? []
: U extends T
? [U, ...LiteralList<Exclude<T, U>>]
: [];
I'm not sure though if it makes sense since I asked ChatGPT to do it.
Is it possible to add this type helper to type-fest?
I realized that this:
type LiteralList<T extends string, U = T> = [T] extends [never]
? []
: U extends T
? [U, ...LiteralList<Exclude<T, U>>]
: [];
for some reason is really slowing down the TS language server.
Is there maybe another way to achieve this?
Related to https://github.com/sindresorhus/type-fest/pull/686
@macmillen The LiteralList type generates all possible combinations of tuples for a given union type, which will grow quite quickly. For example, for a union of 3 members, LiteralList produces a union of 6 (3!) tuples and for a union of 6 members, it results in 720 (6!) tuples.
So, LiteralList is practical only for small unions, upto 4/5 members.
type T = LiteralList<"a" | "b" | "c">;
// ^? type T = ["a", "b", "c"] | ["a", "c", "b"] | ["b", "a", "c"] | ["b", "c", "a"] | ["c", "a", "b"] | ["c", "b", "a"]
@sindresorhus WDYT, should we add this type, probably with a check that ensures the union is small enough?
Sure, we can add it for small sets of values. I'm not sold on the name though.
Some alternatives:
- UnionPermutation
- ExhaustiveTuple
- ExhaustiveList
- CompleteList
- AllCases
@macmillen as @som-sm said this type is not practical to add the way u suggested. But u can instead make a validator that does the same by checking if the List giving is having one of each member in the Union as follows:
Type
// Helpers
type JoinUnion<U, S extends string = ','> =
UnionToTuple<U> extends infer Tuple extends JoinableItem[]
? Join<Tuple, S>
: '';
type TupleOfUnions<U> = UnionToTuple<U>['length'] extends infer Length extends number
? Readonly<BuildTuple<Length, U>>
: never;
type TupleAsString<T, S extends string = '\', \''> = `['${
[T] extends [JoinableItem[]]
? Join<T, S>
: JoinUnion<T, S>
}']`;
// Main Type
type LiteralList<
T extends readonly any[],
U extends readonly any[] | any,
> = (
([U] extends [readonly any[]] ? U : TupleOfUnions<U>) extends infer V extends readonly any[]
? V['length'] extends T['length']
? Exclude<T[number], V[number]> extends infer TnV
? Exclude<V[number], T[number]> extends infer VnT
? IsNever<TnV> extends true
? IsNever<VnT> extends true
? T
: never | `Type ${TupleAsString<T>} is missing Properties: ${TupleAsString<VnT>}`
: never | `Type ${TupleAsString<T>} has extra Properties: ${TupleAsString<TnV>}`
: never
: never
: never | `Type ${TupleAsString<T>} is not the required Length of: ${V['length']}`
: never
);
Use:
type Union = 'a' | 'b' | 'c' | 'd';
type UnionList = TupleOfUnions<Union>;
// ^? [Union, Union, Union, Union]
type foo1 = LiteralList<['a', 'b', 'c', 'd'], Union>; // u can pass in a Union
// ^? ['a', 'b', 'c', 'd']
type foo2 = LiteralList<['a', 'b', 'c'], UnionList>; // or pass in a Tuple made of that Union
// ^? never | Type ['a', 'b', 'c'] is not the required Length of: 4
type foo3 = LiteralList<['a', 'b', 'c', 'd', 'e'], Union>;
// ^? never | Type ['a', 'b', 'c', 'd', 'e'] is not the required Length of: 4
type foo4 = LiteralList<['a', 'b', 'c', 'e'], UnionList>;
// ^? never | Type ['a', 'b', 'c', 'e'] has extra Properties: ['e']
type foo5 = LiteralList<['a', 'b', 'd', 'd'], UnionList>;
// ^? never | Type ['a', 'b', 'd', 'd'] is missing Properties: ['c']
declare function func<T extends Union[] /* or UnionList */>(x: T): typeof x;
const bar1 = ['b', 'd', 'a', 'c'] as const
const bar2 = ['a', 'b', 'c', 'c'] as const
func(bar1 satisfies LiteralList<typeof bar1, Union>); // Fine
func(bar2 satisfies LiteralList<typeof bar2, Union>); // Error: Type ['a', 'b', 'c', 'c'] is missing Properties: ['d']
The string that get returned are just visual Error like They can be removed.