97

I have the following structure:

interface Test1 {
    number: number;
}
interface Test2 extends Test1 {
    text: string;
}

let test: Test1[] | Test2[] = [];
test.map(obj => {}); // does not work

I am getting the error:

Cannot invoke an expression whose type lacks a call signature. Type '{ (this: [Test1, Test1, Test1, Test1, Test1], callbackfn: (this: void, value: Test1, index: nu...' has no compatible call signatures

How can I map over the test variable?

1

1 Answer 1

187

Edit for 4.2

map has become callable now, but you still need an explicit type annotation on its argument to get it to work as expected (the type parameter is not contextually typed)

let test: Test1[] | Test2[] = [];
test.map((obj: Test1 | Test2) => {});

Playground Link

This situation is likely to improve in future versions making this answer mostly obsolete (see PR that will correctly synthesize contextual types for parameters)

Original answer pre 4.2

The problem is that for union types, members which are functions will also be typed as union types, so the type of map will be (<U>(callbackfn: (value: Test1, index: number, array: Test1[]) => U, thisArg?: any) => U[]) | (<U>(callbackfn: (value: Test2, index: number, array: Test2[]) => U) Which as far as typescript is concerned is not callable.

You can either declare an array of the union of Test1 and Test2

let test: (Test1 | Test2)[] = [];
test.map(obj => {}); 

Or you can use a type assertion when you make the call:

let test: Test1[] | Test2[] = [];
(test as Array<Test1|Test2>).map(o=> {});
Sign up to request clarification or add additional context in comments.

7 Comments

Does Array<Test1|Test2> indicate the array might be filled with a combination of both types? So it might be [Test1, Test1, Test2, Test1, Test2, ...]. Where (Test1 | Test2)[] or Test1[] | Test2[] would be all Test1 or all Test2?
@mtpultz Array<Test1|Test2> and (Test1 | Test2)[] are the same and indicate [Test1, Test1, Test2, Test1, Test2, ...]. Test1[] | Test2[] indicates all items will be Test1 or all Test2
@RanLottem since the original post TS has changed the way it allows such calls. Map is still not callable because of the generic type parameter. every has no generic type parameter so TS can create a merged version of it that is callable. This is the relevat PR: github.com/Microsoft/TypeScript/pull/29011
See Improved behavior for calling union types in TS3.3 as well as the caveats that explain why map() still doesn't work
Looks like microsoft/TypeScript#42620 merged into TS4.3 has finally fixed this (at least if you use --noImplicitAny, which is weird)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.