最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

typescript - This comparison appears to be unintentional because the types 'this' and 'Derived'

programmeradmin1浏览0评论

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
  • It's a weirdly worded error. It used to be even worse and say "this comparison will always return 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 to Base, 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:22
  • @jcalz That mostly address the question, but I'm wondering what exactly is meant by "neither one is assignable directly to the other". If I had a const Base instead of this it wouldn't give the error, but it's still not possible to directly assign anything to something const (just like it isn't possible to assign anything to this). – Donald Duck Commented Jan 19 at 16:33
  • I'm not talking about reassigning variables. I'm talking about the types. "Assignable to" is more or less the same thing as "Subtype of", see the handbook. Type X is assignable to type Y if you can take a value of type X and assign it to a (new) variable of type Y. So if the second line of const x: X = ⋯; const y: Y = x succeeds. So const v: this = bar and const v: Derived = this both fail, but const v: Base = this succeeds. – jcalz Commented Jan 19 at 16:53
  • (let me know if I should proceed with answering or finding a duplicate. Does it make sense now or is something missing?) – jcalz Commented Jan 19 at 17:09
  • 1 @jcalz So if I understand correctly, TypeScript checks if the assignment is OK by checking if any of const v: this = bar and const 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
Add a comment  | 

1 Answer 1

Reset to default 2

The 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

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论