TypeScript: how to declare array of fixed size for type checking at Compile Time

benjaminz picture benjaminz · Feb 24, 2017 · Viewed 18.6k times · Source

Update: These checks are meant for compile time, not at runtime. In my example, the failed cases are all caught at compile time, and I'm expecting similar behaviour for the other should-fail cases.

Suppose I'm writing a table-like class where I want all members of the class to be arrays of the same length, something like:

class MyClass {
  tableHead:  string[3]; // expect to be a 3 element array of strings
  tableCells: number[3]; // expect to be a 3 element array of numbers
}

The closest solution I've found so far is:

class MyClass {
  tableHead:  [string, string, string];
  tableCells: [number, number, number];
}

let bar = new MyClass();
bar.tableHead = ['a', 'b', 'c']; // pass
bar.tableHead = ['a', 'b'];      // fail
bar.tableHead = ['a', 'b', 1];   // fail

// BUT these also pass, which are expected to fail at compile time
bar.tableHead = ['a', 'b', 'c', 'd', 'e']; // pass
bar.push('d'); // pass
bar.push('e'); // pass

Any better ideas?

Answer

Huy Nguyen picture Huy Nguyen · Feb 24, 2017

Update 2: From version 3.4, what the OP asked for is now fully possible with a succinct syntax (Playground link):

class MyClass {
  tableHead: readonly [string, string, string]
  tableCells: readonly [number, number, number]
}

Update 1: From version 2.7, TypeScript can now distinguish between lists of different sizes.

I don't think it's possible to type-check the length of a tuple. Here's the opinion of TypeScript's author on this subject.

I'd argue that what you're asking for is not necessary. Suppose you define this type

type StringTriplet = [string, string, string]

and define a variable of that type:

const a: StringTriplet = ['a', 'b', 'c']

You can't get more variables out of that triplet e.g.

const [one, two, three, four] = a;

will give an error whereas this doesn't as expected:

const [one, two, three] = a;

The only situation where I think the lack of ability to constrain the length becomes a problem is e.g. when you map over the triplet

const result = a.map(/* some pure function */)

and expect that result have 3 elements when in fact it can have more than 3. However, in this case, you are treating a as a collection instead of a tuple anyway so that's not a correct use case for the tuple syntax.