I have a class Base
and a class Derived
that inherits from it. Base
has a method that takes a Derived
as a parameter and compares this
to it:
class Base {
foo(bar: Derived): void {
if(this === bar){
return;
}
//Do stuff
}
}
class Derived extends Base {
baz(): void {}
}
Playground link
When I try to compile it, I get this error:
error TS2367: This comparison appears to be unintentional because the types
'this' and 'Derived' have no overlap.
3 if(this === bar){
~~~~~~~~~~~~
I don't understand why I get this error, this
and Derived
are both of type Base
so they do have an overlap. If I were to do const x = new Derived(); x.foo(x);
it would enter the if
block, so the comparison is not useless as the compiler seems to think.
Interestingly, if I remove the baz()
method so that Derived
has an empty body, the error goes away (but that doesn't solve my problem since there's not much point in having a class with an empty body).
Why do I get this error and how do I fix it?
I have a class Base
and a class Derived
that inherits from it. Base
has a method that takes a Derived
as a parameter and compares this
to it:
class Base {
foo(bar: Derived): void {
if(this === bar){
return;
}
//Do stuff
}
}
class Derived extends Base {
baz(): void {}
}
Playground link
When I try to compile it, I get this error:
error TS2367: This comparison appears to be unintentional because the types
'this' and 'Derived' have no overlap.
3 if(this === bar){
~~~~~~~~~~~~
I don't understand why I get this error, this
and Derived
are both of type Base
so they do have an overlap. If I were to do const x = new Derived(); x.foo(x);
it would enter the if
block, so the comparison is not useless as the compiler seems to think.
Interestingly, if I remove the baz()
method so that Derived
has an empty body, the error goes away (but that doesn't solve my problem since there's not much point in having a class with an empty body).
Why do I get this error and how do I fix it?
Share Improve this question asked Jan 19 at 15:58 Donald DuckDonald Duck 8,88223 gold badges79 silver badges102 bronze badges 5 |1 Answer
Reset to default 2The wording of the error message is probably misleading. It's reasonable to interpret "no overlap" as meaning that it's impossible to have a value which is both of type this
and of type Derived
, which is obviously not true. The error message used to be even worse and say "this comparison is always false
" instead of "this appears to be unintentional". That was changed, see microsoft/TypeScript#27910, but the confusion about "no overlap" remains, and people have filed issues against it like microsoft/TypeScript#60583 and microsoft/TypeScript#44645 and microsoft/TypeScript#37155. Some of those are classified as bugs, but generally when an explanation is given by the TS team, it's that the existence of an error is not a bug, but the message could be improved.
What's actually happening is that TypeScript has various heuristics for determining if the use of a comparison operator like ===
is likely to be intentional or unlikely to be intentional. To a first approximation, the rule is: expr1 === expr2
is allowed if the type of expr1
is compatible with the type of expr2
, or vice versa. "compatible with" here means something like "assignable to" or "a subtype of" or "a narrowing of". The details aren't super important.
And in your case, you can't assign this
to a value of type Derived
, nor can you assign bar
to a value of type this
, so neither one is assignable to the other. So TypeScript sees this as an indication that your comparison might be unintentional. There are similar situations where developers would be happy to get that error message because they really did write the wrong thing. As mentioned in a comment of microsoft/TypeScript#44645:
These are the kinds of checks where when we tighten them even slightly, we find huge numbers of actual bugs in real code.
There are all kinds of mistakes you can make with
===
which are not provably always-false but very unlikely to be intentional. Issuing a small number of false positives in weird code to get true positives in bad code is the trade-off that all typecheckers make on purpose.
In situations where TypeScript flags your comparison as likely-unintentional but you really do mean it, you can widen one of the sides of the comparison to a supertype of the other. As you said, this
and Derived
are both of type Base
. So widen either side to Base
and the error goes away:
if (this as Base === bar) { }
if (this === bar as Base) { }
The easiest approach is to use a type assertion to widen, but since type assertions can also unsafely narrow, you might prefer to avoid or supplement it with something safer:
const thisBase: Base = this; if (thisBase === bar) { }
if (this satisfies Base as Base === bar) { }
No matter what, though, you have to jump through a little hoop to convince TypeScript that you're doing this on purpose. It's not optimal, but for every "oh come on, this is obviously intentional" there turns out to be quite a few "oh, this was actually a mistake". These sorts of things are always heuristic in nature, and since developer intent can't be captured perfectly, the best they can do is try to find the spot where the tradeoff maximizes the average developer experience.
Playground link to code
false
". They don't really mean "no overlap", they mean "neither one is assignable directly to the other". See ms/TS#27910. If you want to allow the comparison you need to widen one side, like toBase
, as shown here (you don't need to assert, there are other approaches, it's just the most expedient). Does that fully address the q? If so I'll write an a or find a duplicate. If not, what's missing? – jcalz Commented Jan 19 at 16:22const Base
instead ofthis
it wouldn't give the error, but it's still not possible to directly assign anything to somethingconst
(just like it isn't possible to assign anything tothis
). – Donald Duck Commented Jan 19 at 16:33X
is assignable to typeY
if you can take a value of typeX
and assign it to a (new) variable of typeY
. So if the second line ofconst x: X = ⋯; const y: Y = x
succeeds. Soconst v: this = bar
andconst v: Derived = this
both fail, butconst v: Base = this
succeeds. – jcalz Commented Jan 19 at 16:53const v: this = bar
andconst v: Derived = this
are OK rather than by checking if the actually overlap. That answers the why, and the snippet from your first comment answers how to fix it, so you can post that as an answer (unless you can find a duplicate). – Donald Duck Commented Jan 19 at 17:13