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?
>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
One more sanity check, this also works when using the augment described above…
const baz: [number, ...number[]] = foo.map((x) => x.length);