John White John White - 1 month ago 10x
TypeScript Question

Typescript compiler won't allow methods on an union of two array types to be called

Please consider the following function:

function getArray(): string[] | number[] {
return [];

Long story short, when I do this:

getArray().forEach(item => console.log(item));

The compiler gives me this:

TS2349 Cannot invoke expression whose type lacks a call signature.

Why is this happening? They way I see it,
should be able to be called here without errors, as as far as the compiler is concerned, it is certain that an array will be returned. What is interesting is that IntelliSense will offer autocompletion for this method as usual, but after going with the autocomplete suggestion the error message will show in output.

Is this a bug, or am I missing something trivial here?

Edit: I could use various workarounds, like returning
Array<string | number>
instead, or using overloads, but I'm particularly interested in why returning an union of two array types disallow method invocation.

It is certain that the type is an array, with elements of type
string | number


In general, there's not a good "safe" way to process method calls of union types where the methods have different signatures (TypeScript will allow method calls when those methods have identical signatures) as if there were only one method.

Consider this example:

class ValueHaver<T> {
    value: T;
    update(x: () => T) { this.value = x(); }

let x: ValueHaver<number> | ValueHaver<string> = /*...*/;
x.update(() => 42);

This code is not correct. If x turned out to be ValueHaver<string>, then you'd have an number sitting where a string ought to be.

But this call is just as valid (presuming some hypothetical set of rules) as a forEach invocation of an Array<number> | Array<string>.

You might say "Well, you could try invoking the method X.y for each possible type of X in a union". In theory this could work, but in practice (and especially in TypeScript 2.0) you can easily get union types that have dozens of constituents. The only way to know that a specific type will work is to typecheck the entire call, and its arguments (because of contextual typing), all over again, which would be prohibitively expensive. And for technical reasons the TS compiler isn't generally capable of this -- once the type of a given expression is resolved, it's cached.