Is there a way to declare a successive string literal union in Typescript

Viewed 121

I want to declare a string literal union type: "10" | "11" | ... "99".

I came up with a workaround, but didn't work

const arr = Array.from({
  length: 90
}, (_, i) => i + 10) as const; // expected arr type is readonly [10, 11, ..., 99]
// If above works, then
type NumberToString<T extends number> = `${T}`;
type Successive = NumberToString<typeof arr[number]>;

I wonder if there is a way to quickly define "10" to "99" on top of type level?

2 Answers

This should be possible in next (4.5) version of TypeScript where type instantiation depth limit should be increased

type _NextValue<From, LastValue, R extends any[]> = LastValue extends number 
    ? R['length'] 
    : R['length'] extends From ? From : null

/**
 * Fill array with `null` placeholders until index `From`
 * Then append array length as a member until getting to `To` length
 * Example: `_Values<5, 11>` will result in `[null, null, null, null, null, 5, 6, 7, 8, 9, 10]`
 */

type _Values<From, To, R extends any[] = [], LastValue = null> = R['length'] extends To ? R : 
    _Values<
        From, 
        To, 
        [...R, _NextValue<From, LastValue, R>], 
        _NextValue<From, LastValue, R>
    >

/**
 * Exclude nulls and convert to strings
 */
type NumbersToStrings<T extends Array<number | null>> = `${NonNullable<T[number]>}`;

type N10_99 = NumbersToStrings<_Values<10, 100>> // "10" | "11" | "12" | "13" | "14" | "15" | ... | "99"

Playground

I found a different approach to solve this question, while @Aleksey L. fills Tuple with beginning nulls and records last pushed value, my approach is to use a TempTuple whose length is Start and grows as ResTuple grows.

type AddOne<T extends number, Tuple extends unknown[] = []> = 
    Tuple["length"] extends T ? [1, ...Tuple]["length"] : 
        AddOne<T, [1, ...Tuple]>; 

/** fill tuple to a fixed length */
type FillTuple<T extends number, Tuple extends unknown[] = []> = 
    Tuple["length"] extends T ? Tuple : 
        FillTuple<T, [1, ...Tuple]>; 

/** TempTuple is meant to grow as ResTuple grow */
type RangeTuple<Start extends number, End extends number, ResTuple extends unknown[] = [], TempTuple extends unknown[] = FillTuple<Start>> =
    TempTuple["length"] extends AddOne<End> ? ResTuple : 
        RangeTuple<Start, End, [...ResTuple, `${TempTuple["length"]}`], [1, ...TempTuple]>;

{
    type _ = RangeTuple<10, 99>[number];
}

playground

Related