Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript array push method can't catch a tuple type of the array

const testArray:[number, string] = [10, 'test', 's'];

It doesn't work.

const testArray:[number, string] = [10, 'test']; // It's been edited.
testArray.push('test');

It works.

I think second code shouldn't worked.
Why does the second code work? Is it a bug?


-Added-

I've been thinking about the problem.
Compiler can catch errors only in compile time. So the compiler doesn't catch the error. Is it right?

like image 210
seongkuk han Avatar asked Sep 25 '20 18:09

seongkuk han


2 Answers

This is a good question, and I'll try to rephrase it as explicitly as possible:

Tuple types are a type of array of known length and where the different elements may have different types. A value of type [number, string] is guaranteed to have a length of 2, with a number at element 0 and a string at element 1.

Why then, does TypeScript allow you to call methods like push(), pop(), shift(), unshift(), and splice() on values of tuple types, when such methods generally destroy the supposed guarantees of tuple types? Shouldn't it stop you from doing that, just like it stops you from assigning a value like [1, "two", "three"] to [number, string] in the first place?

Yeah, it's not great.

I don't know that there's a good canonical answer to this. The closest I can find is microsoft/TypeScript#6325, which proposes that such methods be omitted from tuple types. This proposal was declined, on the possible grounds that it would be a breaking change for existing real-world code.

A suggested alternative looks like

type StrictTuple<T extends any[]> =
    Omit<T, keyof (any[])> extends infer O ? { [K in keyof O]: O[K] } : never;

which looks less like an array and more like a set of numeric-key properties:

const x: StrictTuple<[number, string]> = [1, ""] // {0: number; 1: string }
x[1] = "okay";
x[0] = 123;
x.push(123); // error!
//~~~~ Property 'push' does not exist on type { 0: number; 1: string; }

If you really care about such things, you might want to use something like StrictTuple above, but it's probably more trouble than it's worth. Tuple types are ubiquitous in TypeScript and if you use a form that is not assignable to them, then you will have to jump through a lot of unfortunate hoops to use TypeScript.

Pragmatically speaking, I'd say just try not to mutate tuples.

It's possible that someone might open a new issue referencing microsoft/TypeScript#6325 asking to reconsider this now that tuples have gotten a little more strict in the intervening time. It would be an interesting exercise to see what specifically would break if push/pop/etc were omitted from tuple types.

Playground link to code

like image 134
jcalz Avatar answered Sep 21 '22 19:09

jcalz


[Removed previous answer since I misinterpreted.]

Looks like this could be an issue related to type widening? You can declare your tuple to be readonly or as const to prevent the push function from working on the tuple at all

const testArray:readonly [number, string] = [10, 'test'] as const;
testArray.push('test') // error

I found this medium article on the topic: https://blog.logrocket.com/const-assertions-are-the-killer-new-typescript-feature-b73451f35802/

like image 31
Kaiser Pister Avatar answered Sep 23 '22 19:09

Kaiser Pister