To achieve this, we must develop a versatile utility type that generates a tuple of the desired length without using mutable Array.prototype methods.
Take a look at this:
type WithoutIndex<T> = Omit<T,number>
type MutableProps = 'push' | 'splice' | 'shift' | 'unshift' | 'pop' | 'sort'
type Tuple<
Length extends number,
Type,
Result extends Type[] = []
> =
(Result['length'] extends Length
? WithoutIndex<Omit<Result, MutableProps>>
: Tuple<Length, Type, [...Result, Type]>)
Tuple
recursively invokes itself until the length of the Result
matches the provided Length
argument.
Here is the JavaScript representation:
const Tuple = (length: number, result: number[] = []) => {
if (length === result.length) {
return result
}
return tuple(length, [...result, 1])
}
WithoutIndex
simply prohibits the use of any numeric indexes that do not exist in the tuple.
Let's test if it functions properly:
type WithoutIndex<T> = Omit<T, number>
type MutableProps = 'push' | 'splice' | 'shift' | 'unshift' | 'pop' | 'sort'
type Tuple<
Length extends number,
Type,
Result extends Type[] = []
> =
(Result['length'] extends Length
? WithoutIndex<Omit<Result, MutableProps>>
: Tuple<Length, Type, [...Result, Type]>)
let person: {
name: string;
age: number;
hobbies: string[];
role: Tuple<2, string>
} = {
name: 'stack overflow',
age: 30,
hobbies: ['reading', 'jogging'],
role: ['reading', 'jogging'],
};
person.role[1] = 'reader'; //ok
person.role[10] = 'reader'; // expected error
person.role.push(); //allowed, TS compiler should prevent it
person.role.sort // expected error
Interactive Playground
Diverse tuple
type WithoutIndex<T> = Omit<T, number>
type MutableProps = 'push' | 'splice' | 'shift' | 'unshift' | 'pop' | 'sort'
type DiversityTuple<T extends readonly any[]> = WithoutIndex<Omit<T, MutableProps>>
const diffTUple: DiversityTuple<[string, number]> = ['hello', 42]
diffTUple[0] = 'a' // ok
diffTUple[1] = 1 // ok
diffTUple[10] = 1 // expected error
diffTUple[0] = 1 // expected error
diffTUple[1] = 'b' // expected error