In order to do that, we need to create a generic utility type wich will create a tuple with expected length but without mutable Array.prototype methods.
Consider 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
calls itself until length of Result
will be the same as provided Length
argument.
Here you have js representation:
const Tuple = (length: number, result: number[] = []) => {
if (length === result.length) {
return result
}
return tuple(length, [...result, 1])
}
WithoutIndex
just forbids using any numeric indexes which are not exist in tuple.
Lets try if it works:
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
Playground
Diversity 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
readonly [string, string]
. – Seldanreadonly
then that value cannot be changed at all.person.role[1] = 'reader';
fails to compile. That's not the intention. The idea is to prevent compile time issue. – Nonbeliever