-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Compilation fails because tsc incorrectly believes "types 'A' and 'B' have no overlap" #31555
Comments
Duplicate of #17445 |
This should no longer be an error now that #30637 has landed. With that change
True, but devil's advocate here: They are more likely (from the compiler's point of view) to be different types--because why have two type parameters otherwise--and this is why the compiler is producing an error. The code as written is not provably sound. The error message itself ("condition is always true") is fallacious, but the compiler is (IMO) within its rights to flag an error here. I'm of mixed opinion here. I would personally want this to be an error: let x: string[] = { "pig", "cow" };
let y: number[] = [ 812, 1208 ];
arraysEqual(x, y); // can't possibly be true! I would therefore probably declare function arraysEqual<A extends B, B>(a: A[], b: B[]): boolean
{
/* magic goes here */
}
let strs: string[] = [ "pig", "cow" ];
let nums: number[] = [ 812, 1208 ];
let both: (string | number)[] = [ "pig", 812, "cow", 1208 ];
arraysEqual(strs, both); // OK, types are related
arraysEqual(nums, both); // also OK
arraysEqual(strs, nums); // error, unrelated types I don't think there's a way you can make |
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 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 |
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. |
@fatcerberus This was the correct choice for my case, since it isn't important that compilation succeeds if the two arrays are of different types. I rewrote my function as you suggested, but I still created the issue because as you acknowledged, the error message is strictly speaking incorrect. Commenters on #17445 made a much better case than I could for why this shouldn't be an error, but the workarounds are straightforward enough that the mere fact that this is produces an error doesn't particularly bother me. If it was up to me, the original snippet would compile without errors, but this isn't a hill I would die on. I do think that, at the very least, the error message needs to be changed to no longer be inconsistent. Incorrect error messages should certainly qualify as buggy behavior!
@RyanCavanaugh While I am not going to debate the basic point of whether this code should produce an error or not - my primary concern is that really just that I think it should not be producing this error in particular - I don't think that your argument is especially applicable to this situation. Generics are not templates, and TypeScript is not really a strongly-typed language. Such a comparison between two generic types might be clearly invalid in a statically-typed language such as C++, but in JavaScript it's all fine, and the reason that TypeScript gives for the compilation failure is simply not productive. |
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 |
@fatcerberus I won't argue with that! I still think the error message should be improved, though. |
We're open to suggestions on error message wording. Just curious, do you think |
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
Perhaps, Comparison between 'A' and 'B' is not allowed because the operation cannot be proven to be meaningful.
It's my opinion that strict equality/inequality comparison should only produce an error like this when it's absolutely guaranteed that the comparison is a no-op, assuming the type annotations accurately reflect the values being operated upon. |
I don't think that's ever absolutely 100% guaranteed because we have intersection types. I consider it the same case with generics, just at a higher level - two type parameters could be the same type, but it's more likely they're different, so treating them as though they're always the same isn't sound. |
It can be guaranteed when A and B are not generic types, such as when
comparing a number to a string. Again, assuming that the typing is accurate.
When both are generics, I don't think this is a particularly useful error.
I certainly think that if an error will occur, then the message should more
accurately describe the reason for the compilation failure.
|
Only when |
For what it's worth I kind of see where you're coming from with generics, but considering that we already have ways to tell the compiler that two parameters are related: function fn<A, B extends A>(a: A, b: B) {
// now a and b can be compared
}
function fn2<T>(a: T, b: T) {
// call site is only valid if a and b are related
} I would rather that |
is a better error message than
|
That's ok with me, but still - the current error message is not great. |
The "why use two different type parameters" argument is only half valid: type Size = 'big' | 'small';
class Shirt<T extends Size> {
constructor(readonly size: T) {}
is<O extends Size>(size: O): this is Shirt<O> {
return size === this.size;
}
} The |
TypeScript Version: 3.2.4
Search Terms: generics, strict equality, overlap
Code
Here is a function I wrote, to serve as a repro case.
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.
The text was updated successfully, but these errors were encountered: