Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why doesn't the `map()` return type maintain the same number of values as the input?

I have some TypeScript code that goes sort of like this:

const foo: [string, ...string[]] = ['narf']
const bar: [string, ...string[]] = foo.map((x) => x)

The error I’m getting is: Type 'string[]' is not assignable to type '[string, ...string[]]'. ts(2322)

Well, yes, but map() never changes the number of elements in the list, so why doesn’t it return the type [string, ...string[]]? And what is the recommended fix for this?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

We simply don’t have a good way to represent this that catches all the edge cases while being sensible to add to the built-in’s typings. While you could use something like this to make it work,

interface Array<T> {
    map<V>(predicate: (value: T, index: number, array: Array<T>) => V): { [K in keyof this]: V };
}

It’s probably going to fail in some cases (I already found one – mapping never[] results in unknown[]), and while we can patch those individual cases (either with more overloads or conditional types), it’s simply not feasible to use or maintain.

This does work but I would not recommend augmenting built-ins like this…

const foo: [string, ...string[]] = ['narf'];
const bar: [string, ...string[]] = foo.map((x) => x); // okay

Instead, I would just use an assertion, so that other TypeScript developers that look at the code don’t wonder, "wait, why does this work?"

const foo: [string, ...string[]] = ['narf'];
const bar: [string, ...string[]] = foo.map((x) => x) as [string, ...string[]]; // okay

You are of course, free to use whichever way you’d like.

Related: microsoft/TypeScript#29841

Playground


One more sanity check, this also works when using the augment described above…

const baz: [number, ...number[]] = foo.map((x) => x.length);
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading