TypeScript: Union array types do not infer common array prototype methods

TypeScript Version: 2.0.2rc

Code

const foo = (): number[] | string[] => Math.random() > 0.5 ? [0, 1, 2] : ["0", "1", "2"];
const bar = foo().filter((x) => Number(x) % 2 === 1);

Expected behavior: No errors.

Actual behavior: Compiler gives: error TS2349: Cannot invoke an expression whose type lacks a call signature.

This issue seems to be array-specific. The following code compiles as expected:

const foo = (): { a } | { b } => Math.random() > 0.5 ? { a: true } : { b: true }
const bar = foo().hasOwnProperty('a');

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 15 (12 by maintainers)

Most upvoted comments

This is a bit subtle, but number[] | string[] !=== (number | string)[]. The first one has an assert on homogeneity, while the second does not. just as in the example noted above by @kaotik4266, [0, "1", 2] is not of type number[] | string[].

There is not a meaningful way to describe the general case for merging unmatching signatures; while filter might be arguably safe, push is not. So merging the signatures of (number[]|string[]).push(...) th same proposed for filter would result in push(a: number | string); which means that we are treating the original type as (number | string)[], which is not type safe.

So the general case, is there is no common signatures between the two types, and thus the resulting union type does have a filter property but it does not have a signature, and hence the error message Cannot invoke an expression whose type lacks a call signature.

Workaround:

const foo = (): (number | string)[] => ...

(Note this unveils an expected second error in the snippet because number % number yields number and .filter expects boolean)