microsoft/TypeScript

Can't call `forEach` on `number[] | ReadonlyArray<never>`

Opened this issue · 2 comments

TypeScript Version: nightly (2.5.0-dev.20170623)

Code

const empty: ReadonlyArray<never> = [];
function f(nums: number[] | undefined) {
    const a = nums || empty;
    a.forEach(n => {});
}

Expected behavior:

No error.

Actual behavior:

4     a.forEach(n => {});
      ~~~~~~~~~~~~~~~~~~

src/a.ts(4,5): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((callbackfn: (value: never, index: number, array: ReadonlyArray<never>) => void, thisArg?: any) ...' has no compatible call signatures.


4     a.forEach(n => {});
                ~

src/a.ts(4,15): error TS7006: Parameter 'n' implicitly has an 'any' type.
jcalz commented

I think the first error message is from the same issue reported in #16644:

var a = [1, 2, 3];
(a as number[]).filter(x => typeof x === 'string'); // fine
(a as (number | string)[]).filter(x => typeof x === 'string'); // fine
(a as number[] | (number | string)[]).filter(x => typeof x === 'string'); // error!

var b = [];
(b as number[]).forEach(x => console.log(x)); // fine
(b as never[]).forEach(x => console.log(x)); // fine
(b as number[] | never[]).forEach(x => console.log(x)); // error!

In that issue, @kujon was asking that A[] | B[] should reduce to (A|B)[] which is consistent with the way TypeScript currently treats arrays as covariant in their content type. But possibly-mutable arrays should be invariant in their type, so maybe that fix would be going farther in the wrong direction.

The other way of looking at this issue is:

type Overloaded<A, B> = {
  (a: A): void;
  (b: B): void;
}
function exploded<A, B>(f: Overloaded<A,B>, ab: A | B): void {
  f(ab); // error!
  var isA: (ab) => ab is A;
  isA(ab) ? f(ab) : f(ab); // workaround, no error 
}

TypeScript can't take an overloaded or generic function and realize that if it can resolve a parameter of type A and separately resolve a parameter of type B, then it "should" be able to handle a parameter of type A|B. But here be dragons.

Thanks, I'll mark this as a duplicate of that issue.