7

Given this slightly artificial example:

['List', 'Of', 'Names']
        .map((name, index) => [name, index % 2])
        .map(([name, num]) => );

why is name and num in the last line of type string | number obviously inferred as an Array of strings and numbers and do any of you know if there is a way to use type inference so that name is a string and num is a number respectively?

cmart
  • 991
  • 3
  • 11
  • 19

2 Answers2

17

You can use a const assertion:

['List', 'Of', 'Names']
    .map((name, index) => [name, index % 2] as const) // insert `as const` here
    .map(([name, num]) => { }); // name: string, num: number

Take a look at the playground sample.

bela53
  • 3,040
  • 12
  • 27
  • Very elegant! This is especially useful when you want to extend `useState` result in React but keep similar typing, eg adding a side-effect the the function that change the state. The result becomes `readonly` though. – Eric Burel Feb 04 '21 at 17:24
  • This should be the answer, given that it is the current way to solve this problem albeit the prior answer does have historical information. – Tim Feb 09 '22 at 04:36
  • A downside of this solution is that it produces a readonly tuple. If that's not an issue - works great! – Egor Nepomnyaschih Mar 17 '22 at 19:15
13

For arrays literals type inference does not infer tuples, it infers arrays, so

var foo = ["", 0]; // foo is Array<string | number> not [string, number]

I have not found documentation on this but the pull request adding support for tuples never uses inference when declaring them, I'm guessing this is deliberate.

In your case you can specify the type parameter:

['List', 'Of', 'Names']
        .map<[string, number]>((name, index) => [name, index % 2])
        .map(([name, num]) => name + "");

2.9 and below solution

Or create a tuple helper function if this is a common issue for you:

function tuple<T1, T2, T3, T4, T5>(data: [T1, T2, T3, T4, T5]) : typeof data
function tuple<T1, T2, T3, T4>(data: [T1, T2, T3, T4]) : typeof data
function tuple<T1, T2, T3>(data: [T1, T2, T3]) : typeof data
function tuple<T1, T2>(data: [T1, T2]) : typeof data
function tuple(data: Array<any>){
    return data;
}

['List', 'Of', 'Names']
        .map((name, index) => tuple([name, index % 2]))
        .map(([name, num]) => name + "");

3.0 Solution

Since I posted the original answer typescript has improved it's inference with the ability to infer tuple types for rest parameters. See PR for details. With this feature we can write a shorter version of the tuple function :

function tuple<T extends any[]> (...data: T){
    return data;
}

['List', 'Of', 'Names']
        .map((name, index) => tuple(name, index % 2))
        .map(([name, num]) => name + "");
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • Explicit typing is what I wanted to avoid. I could also use an object and use the object destructuring `.map(({name, num}) => ...)` but also not very concise. Thx for the idea with the tuple func! – cmart Feb 08 '18 at 15:12