You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Compilation fails because tsc incorrectly believes "types 'A' and 'B' have no overlap" #31555
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.
changed the title [-]Compilation fail because tsc incorrectly believes "types 'A' and 'B' have no overlap"[/-][+]Compilation fails because tsc incorrectly believes "types 'A' and 'B' have no overlap"[/+]on May 23, 2019
This should no longer be an error now that #30637 has landed. With that change A and B are considered related by way of unknown.
A and B could very well be the same type. The error isn't logical.
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:
letx: string[]={"pig","cow"};lety: number[]=[812,1208];arraysEqual(x,y);// can't possibly be true!
I would therefore probably declare arraysEqual as (note this also fixes the error in the OP):
functionarraysEqual<AextendsB,B>(a: A[],b: B[]): boolean{/* magic goes here */}letstrs: string[]=["pig","cow"];letnums: number[]=[812,1208];letboth: (string|number)[]=["pig",812,"cow",1208];arraysEqual(strs,both);// OK, types are relatedarraysEqual(nums,both);// also OKarraysEqual(strs,nums);// error, unrelated types
I don't think there's a way you can make A and B interchangeable and still keep the above error, however.
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.
Reacted by Titian Cernicova-Dragomir and Bruce Pascoe
Using a single type parameter overall works the best from a type-checking POV too:
functionarraysEqual<T>(a: T[],b: T[]): boolean{if(a.length!==b.length)returnfalse;for(leti=0;i<a.length;i++){if(a[i]!==b[i])returnfalse;}returntrue;}letstrs=["pig","cow"];letnums=[812,1208];letboth=["pig",812,"cow",1208];arraysEqual(nums,both);// OKarraysEqual(strs,both);// still OKarraysEqual(both,strs);// yep, that tooarraysEqual(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.
Using a single type parameter overall works the best from a type-checking POV too
Bruce Pascoe (@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!
If we think about generic functions as C++-style templates
Ryan Cavanaugh (@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 any[] (or unknown[]).
We're open to suggestions on error message wording.
Just curious, do you think === should be allowed with any non-primitive operands? e.g. cat === mortgage should be OK because both sides might be an aliased reference to a CatMortgage?
We're open to suggestions on error message wording.
Perhaps,
Comparison between 'A' and 'B' is not allowed because the operation cannot be proven to be meaningful.
Just curious, do you think === should be allowed with any non-primitive operands?
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. A & B is assignable to either A or B, without a cast. So there's always a chance they could be the same object, regardless of what the types involved are. I don't think I'd want to give up type safety on equality comparisons just because of that small uncertainty.
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.
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.
Only when A and B are primitives (and even then string & number is a valid type for some reason). If they are object types then either one could really be A & B. See Ryan's CatMortgage example above.
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:
functionfn<A,BextendsA>(a: A,b: B){// now a and b can be compared}functionfn2<T>(a: T,b: T){// call site is only valid if a and b are related}
I would rather that <A, B> continue to be treated as though they are different types. The uncertainty factor at the type level is IMO the same as the possibility of an A & B occuring at the value level.
I would rather that <A, B> continue to be treated as though they are different types. The uncertainty factor at the type level is IMO the same as the possibility of an A & B occuring at the value level.
That's ok with me, but still - the current error message is not great.
Activity
[-]Compilation fail because tsc incorrectly believes "types 'A' and 'B' have no overlap"[/-][+]Compilation fails because tsc incorrectly believes "types 'A' and 'B' have no overlap"[/+]jack-williams commentedon May 23, 2019
Duplicate of #17445
fatcerberus commentedon May 23, 2019
This should no longer be an error now that #30637 has landed. With that change
AandBare considered related by way ofunknown.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:
I would therefore probably declare
arraysEqualas (note this also fixes the error in the OP):I don't think there's a way you can make
AandBinterchangeable and still keep the above error, however.RyanCavanaugh commentedon May 23, 2019
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, butarraysEqual<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.fatcerberus commentedon May 23, 2019
Using a single type parameter overall works the best from a type-checking POV too:
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.
pineapplemachine commentedon May 23, 2019
Bruce Pascoe (@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!
Ryan Cavanaugh (@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.
fatcerberus commentedon May 23, 2019
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[](orunknown[]).pineapplemachine commentedon May 23, 2019
Bruce Pascoe (@fatcerberus) I won't argue with that! I still think the error message should be improved, though.
RyanCavanaugh commentedon May 23, 2019
We're open to suggestions on error message wording.
Just curious, do you think
===should be allowed with any non-primitive operands? e.g.cat === mortgageshould be OK because both sides might be an aliased reference to aCatMortgage?typescript-bot commentedon May 26, 2019
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
pineapplemachine commentedon May 27, 2019
Ryan Cavanaugh (@RyanCavanaugh)
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.
fatcerberus commentedon May 27, 2019
I don't think that's ever absolutely 100% guaranteed because we have intersection types.
A & Bis assignable to eitherAorB, without a cast. So there's always a chance they could be the same object, regardless of what the types involved are. I don't think I'd want to give up type safety on equality comparisons just because of that small uncertainty.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.
pineapplemachine commentedon May 27, 2019
fatcerberus commentedon May 27, 2019
Only when
AandBare primitives (and even thenstring & numberis a valid type for some reason). If they are object types then either one could really beA & B. See Ryan'sCatMortgageexample above.fatcerberus commentedon May 27, 2019
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:
I would rather that
<A, B>continue to be treated as though they are different types. The uncertainty factor at the type level is IMO the same as the possibility of anA & Boccuring at the value level.jack-williams commentedon May 28, 2019
Type 'number' is not assignable to type 'string & number'.is a better error message than
Type 'number' is not assignable to type 'never'.pineapplemachine commentedon May 29, 2019
That's ok with me, but still - the current error message is not great.
HaraldKi commentedon Aug 6, 2022
The "why use two different type parameters" argument is only half valid:
The
iscannot be written without a new type argument. The workaround here is to add| Tto the type argument ofis, i.e.is<O extends Size | T>.