2

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, forEach 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.

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

Comments

2

You cannot invoke forEach on the type string[] | number[].

What you probably want is: Array<string | number>:

function getArray(): Array<string | number> {
    return [];
}

var test = getArray().forEach(item => { console.log(item) }); // no error

But if you prefer then you can cast to one of them:

(getArray() as string[]).forEach(item => { /* item is a string */ });

Or you can cast to any:

(getArray() as any[]).forEach(item => { /* item is any */ });

2 Comments

function getArray(): [string | number] { works as well, for the same reason.
[string | number] is a 1-length tuple which is really not clearly communicating the intent of the function!

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.