TypeScript: Compilation fails because tsc incorrectly believes "types 'A' and 'B' have no overlap"

TypeScript Version: 3.2.4

Search Terms: generics, strict equality, overlap

Code

Here is a function I wrote, to serve as a repro case.

export function arraysEqual<A, B>(a: A[], b: B[]): boolean {
    if(a.length !== b.length) return false;
    for(let i = 0; i < a.length; i++) {
        if(a[i] !== b[i]) return false;
    }
    return true;
}

Expected behavior:

The function compiles. It accepts two arrays of any two types and performs a strict equality check on their elements, whether A and B are the same type or not.

Actual behavior:

Compilation error on the line if(a[i] !== b[i]) return false; -

This condition will always return ‘true’ since the types ‘A’ and ‘B’ have no overlap.

A and B could very well be the same type. The error isn’t logical.

I will probably just rewrite it to take a single generic parameter T and both arrays will be of type T[], since the function should work just as well for me in this case. But I still believe that this behavior qualifies as a bug.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 17 (2 by maintainers)

Most upvoted comments

If we think about generic functions as C+±style templates where we just rewrite the function body in terms of the template arguments and then see if that new instantiated body typechecks, then arraysEqual<string, string> would be a valid instantiation, but arraysEqual<string, number> would be an invalid instantiation.

But this is the whole point – if you intended for the two type parameters to have the same-ish type, then you didn’t need two type parameters in the first place. So your intent of having two type parameters must have been that arraysEqual<string, number> was an legal instantiation, therefore it’s the body of the function that has an error.

Duplicate of #17445

(and even then string & number is a valid type for some reason)

Type 'number' is not assignable to type 'string & number'.

is a better error message than

Type 'number' is not assignable to type 'never'.

Based on your description, it sounds like you don’t actually care what the types of the arrays are, just that they’re both arrays - at which point you gain nothing from the generic and could get the same result by declaring the parameters as any[] (or unknown[]).

Using a single type parameter overall works the best from a type-checking POV too:

function arraysEqual<T>(a: T[], b: T[]): boolean {
    if(a.length !== b.length) return false;
    for(let i = 0; i < a.length; i++) {
        if(a[i] !== b[i]) return false;
    }
    return true;
}

let strs = [ "pig", "cow" ];
let nums = [ 812, 1208 ];
let both = [ "pig", 812, "cow", 1208 ];

arraysEqual(nums, both);  // OK
arraysEqual(strs, both);  // still OK
arraysEqual(both, strs);  // yep, that too
arraysEqual(nums, strs);  // this is an error, yay!

As long as the types are related, the call typechecks. If they are completely unrelated, the call is almost surely a mistake and the compiler flags it as such.