Home > Back-end >  Typescript: declare type of index of tuple?
Typescript: declare type of index of tuple?

Time:10-02

Given a TypeScript Tuple e.g.:

const arr = [1, 2] as const;

We get static type checks on indexing:

console.log(arr[1]);

is good, but

console.log(arr[2]);

errors with:

Tuple type 'readonly [1, 2]' of length '2' has no element at index '2'.ts (2493)

which is great.


I'd like to declare a constant to be of the type of the index of that tuple, so that what can be assigned to that constant follows the same constraints (0 | 1).

I tried this:

const index: keyof typeof arr = 2 as const;
console.log(arr[index]);

but TypeScript didn't show any errors. I suspect that index is a number which is too broad.

CodePudding user response:

Yeah, for whatever reason, tuple indices are numeric string literal types like "0" and "1" instead of the corresponding numeric literal types like 0 and 1. There's also a number index signature, so keyof ["a", "b"] will give you number | "0" | "1" | .... Which means just using keyof will let you assign any number whatsoever.

If you want to compute the numeric literals, you can use template literal types to do so, at least in TypeScript 4.8 and above:

type TupleIndices<T extends readonly any[]> =
    Extract<keyof T, `${number}`> extends `${infer N extends number}` ? N : never;

First I Extract the numeric string literal types from the full key set by filtering on `${number}`, the set of all strings that can be parsed as a number. This eliminates number itself (which isn't a string) and also "length" and "slice" and other array members. So that gives us "0" | "1" | "2" | .... Then I use the improved infer types introduced in TypeScript 4.8 to convert those string literals to numeric literals.

That gives you this:

const arr = [1, 2] as const;

type ArrIndices = TupleIndices<typeof arr>;
// type ArrIndices = 0 | 1

type Also = TupleIndices<[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]>;
// type Also = 0 | 1 | 2 | 9 | 3 | 4 | 5 | 6 | 7 | 8

Playground link to code

CodePudding user response:

A bit of a hack, based on tail-recursively defining a range of numbers:

type Enumerate<
    N extends number,
    Acc extends number[] = [],
> = Acc['length'] extends N
    ? Acc[number]
    : Enumerate<N, [...Acc, Acc['length']]>;
type Range<F extends number, T extends number> = Exclude<
    Enumerate<T>,
    Enumerate<F>
>;

const index: Range<0, typeof arr['length']> = 2 as const;
// Type '2' is not assignable to type '0 | 1'. ts(2322)
  • Related