I am trying to build a generic type, that will guarantee presence of the string literal (no matter at what position)
What I desire for:
// 'a' is in the first position
const x1: ArrayContainingLiteral<'a'> = ['a', 'b', 'c']; // ✅
// 'a' is in the last position
const x2: ArrayContainingLiteral<'a'> = ['b', 'a']; // ✅
// 'a' is in between of other elements
const x3: ArrayContainingLiteral<'a'> = ['b', 'a', 'c', 'd']; // ✅
// 'a' is not in the array
const x4: ArrayContainingLiteral<'a'> = ['b', 'c']; // ❌
// 'a' is not in the array (array is empty)
const x5: ArrayContainingLiteral<'a'> = []; // ❌
My current progress is:
export type DoesArrayIncludeLiteral<StringLiteral extends string, StringArray extends Array<string>> = {
[ArrayIndex in keyof StringArray]: StringArray[ArrayIndex] extends StringLiteral ? unknown : never;
}[number] extends never
? false
: true;
export type ArrayContainingLiteral<Literal extends string, StringArray extends Array<string> = Array<string>> =
DoesArrayIncludeLiteral<Literal, StringArray> extends true ? StringArray : never;
Which works somewhat fine but requires the whole array to be passed as an type argument
const x1: ArrayContainingLiteral<'a', ['a', 'b', 'c']> = ['a', 'b', 'c']; // ✅
This can be done for arrays under a certain size, for example:
type Indices = 0 | 1 | 2 | 3 | 4 | 5;
type ArrayContainingLiteral<T> =
Array<unknown> &
(Indices extends infer U extends number ?
U extends unknown ?
{ [I in U]: T }
: never
: never)
For larger sizes, see Is it possible to restrict number to a certain range
import { IntRange } from 'type-fest'
type Indices = IntRange<0, 32>
A naive solution like:
type ArrayContainingLiteral<T> =
| [T, ...unknown[]]
| [unknown, ...ArrayContainingLiteral<T>]
Does not work because of infinite recursion.