4

Does anybody know how to make compiler to infer tuple type automatically ?

// Now: (string | number)[]
// Wanted: [string, number][]
const x = [ ["a", 2], ["b", 2] ];
NN_
  • 1,593
  • 1
  • 10
  • 26
  • 1
    it doesn't look like it can, it can't assume that the inner arrays will always be of the same length. You have to specify that yourself – toskv Feb 19 '18 at 18:49

4 Answers4

4

You can now use the recently added as const to achieve this:

const x = [ ["a", 2], ["b", 2] ] as const;
// Type is
// readonly [readonly ["a", 2], readonly ["b", 2]]

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions

jmathew
  • 1,522
  • 17
  • 29
3

This can be done if we use an extra function to help type inference a bit:

function tupleArray<T1, T2, T3>(arr:[T1, T2, T3][]) : typeof arr 
function tupleArray<T1, T2>(arr:[T1, T2][]) : typeof arr 
function tupleArray<T1>(arr:[T1][]) : typeof arr 
function tupleArray(arr:any[]) : any[]{
    return arr;
}

var t = tupleArray([ ["a", 2], ["b", 2] ]) // [string, number][]

Edit

Better version with fewer overrides:

const tupleArray = <T extends ([any] | any[])[]>(args: T): T => args
tupleArray([["A", 1], ["B", 2]]) // [string, number][]

You can add more overloads if you need more then 3 items in the tuple.

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
0

The compiler does not infer that, and there no way to force it to "know" it. the only thing that you can (And should do) is defined an interface that extends Array. like this:

interface NumStrTuple extends Array<string | number> {
    0: number;
    1: string;
    length: 2;
}

And use it to define your const like this:

const x: NumStrTuple = [ ["a", 2], ["b", 2] ];
gilamran
  • 7,090
  • 4
  • 31
  • 50
0

This answer's single purpose is to demonstrate that it's technically possible to do this. I want to strongly disencourage anyone from using this because it definitely has more than just a few flaws. Anyway I took the challenge, this is the monster I came up with:

type UnionToTuple<
  T,
  U = (
    (T extends any ? () => T : never) extends infer L
      ? boolean extends L
        ? (
            Exclude<L, boolean> & boolean extends any
              ? (p: L) => void
              : never
          ) extends (p: infer M) => void
          ? M
          : never
        : (L extends any ? (p: L) => void : never) extends (
            p: infer N
          ) => void
        ? N
        : never
      : never
  ) extends () => infer U
    ? U
    : never,
  V = [T] extends [never] ? true : false
> = true extends V
  ? []
  : UnionToTuple<Exclude<T, U>> extends any[]
  ? // @ts-ignore (needed because type could be excessively deep and possibly infinite)
    [...UnionToTuple<Exclude<T, U>>, U]
  : never;

type InferTuple<T> = T extends (infer U)[]
  ? U extends (infer V)[]
    ? UnionToTuple<V>[]
    : never
  : never;

const x = [
  ["a", 2],
  ["b", 2]
];

type TupleType = InferTuple<typeof x>;
// TupleType is inferred as [string, number][]

TypeScript Playground

Behemoth
  • 5,389
  • 4
  • 16
  • 40