Skip to content

Commit c3df353

Browse files
Implement fix for this type comparison false positive
Co-authored-by: RyanCavanaugh <[email protected]>
1 parent 6355df5 commit c3df353

5 files changed

Lines changed: 262 additions & 5 deletions

File tree

src/compiler/checker.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22143,11 +22143,26 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2214322143
(source as NumberLiteralType).value === (target as NumberLiteralType).value
2214422144
) return true;
2214522145
if (s & TypeFlags.BigIntLike && t & TypeFlags.BigInt) return true;
22146-
if (s & TypeFlags.BooleanLike && t & TypeFlags.Boolean) return true;
22147-
if (s & TypeFlags.ESSymbolLike && t & TypeFlags.ESSymbol) return true;
22148-
if (
22149-
s & TypeFlags.Enum && t & TypeFlags.Enum && source.symbol.escapedName === target.symbol.escapedName &&
22150-
isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)
22146+
if (s & TypeFlags.BooleanLike && t & TypeFlags.Boolean) return true;
22147+
if (s & TypeFlags.ESSymbolLike && t & TypeFlags.ESSymbol) return true;
22148+
// For comparable relation, revert `this` type parameters back to their constrained class type
22149+
if (relation === comparableRelation) {
22150+
if (s & TypeFlags.TypeParameter && (source as TypeParameter).isThisType) {
22151+
const constraint = getConstraintOfTypeParameter(source as TypeParameter);
22152+
if (constraint && isTypeRelatedTo(constraint, target, relation)) {
22153+
return true;
22154+
}
22155+
}
22156+
if (t & TypeFlags.TypeParameter && (target as TypeParameter).isThisType) {
22157+
const constraint = getConstraintOfTypeParameter(target as TypeParameter);
22158+
if (constraint && isTypeRelatedTo(source, constraint, relation)) {
22159+
return true;
22160+
}
22161+
}
22162+
}
22163+
if (
22164+
s & TypeFlags.Enum && t & TypeFlags.Enum && source.symbol.escapedName === target.symbol.escapedName &&
22165+
isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)
2215122166
) return true;
2215222167
if (s & TypeFlags.EnumLiteral && t & TypeFlags.EnumLiteral) {
2215322168
if (s & TypeFlags.Union && t & TypeFlags.Union && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//// [tests/cases/compiler/thisTypeComparison.ts] ////
2+
3+
//// [thisTypeComparison.ts]
4+
class AA {
5+
do1() {
6+
const b = dd.getB();
7+
if (this === b) {
8+
console.log("this === b");
9+
}
10+
}
11+
}
12+
13+
class BB extends AA {
14+
getB(): BB { return this; }
15+
}
16+
17+
let dd = new BB();
18+
dd.do1();
19+
20+
//// [thisTypeComparison.js]
21+
"use strict";
22+
var __extends = (this && this.__extends) || (function () {
23+
var extendStatics = function (d, b) {
24+
extendStatics = Object.setPrototypeOf ||
25+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
26+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
27+
return extendStatics(d, b);
28+
};
29+
return function (d, b) {
30+
if (typeof b !== "function" && b !== null)
31+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
32+
extendStatics(d, b);
33+
function __() { this.constructor = d; }
34+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
35+
};
36+
})();
37+
var AA = /** @class */ (function () {
38+
function AA() {
39+
}
40+
AA.prototype.do1 = function () {
41+
var b = dd.getB();
42+
if (this === b) {
43+
console.log("this === b");
44+
}
45+
};
46+
return AA;
47+
}());
48+
var BB = /** @class */ (function (_super) {
49+
__extends(BB, _super);
50+
function BB() {
51+
return _super !== null && _super.apply(this, arguments) || this;
52+
}
53+
BB.prototype.getB = function () { return this; };
54+
return BB;
55+
}(AA));
56+
var dd = new BB();
57+
dd.do1();
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//// [tests/cases/compiler/thisTypeComparison.ts] ////
2+
3+
=== thisTypeComparison.ts ===
4+
class AA {
5+
>AA : Symbol(AA, Decl(thisTypeComparison.ts, 0, 0))
6+
7+
do1() {
8+
>do1 : Symbol(AA.do1, Decl(thisTypeComparison.ts, 0, 10))
9+
10+
const b = dd.getB();
11+
>b : Symbol(b, Decl(thisTypeComparison.ts, 2, 13))
12+
>dd.getB : Symbol(BB.getB, Decl(thisTypeComparison.ts, 9, 21))
13+
>dd : Symbol(dd, Decl(thisTypeComparison.ts, 13, 3))
14+
>getB : Symbol(BB.getB, Decl(thisTypeComparison.ts, 9, 21))
15+
16+
if (this === b) {
17+
>this : Symbol(AA, Decl(thisTypeComparison.ts, 0, 0))
18+
>b : Symbol(b, Decl(thisTypeComparison.ts, 2, 13))
19+
20+
console.log("this === b");
21+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
22+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
23+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
24+
}
25+
}
26+
}
27+
28+
class BB extends AA {
29+
>BB : Symbol(BB, Decl(thisTypeComparison.ts, 7, 1))
30+
>AA : Symbol(AA, Decl(thisTypeComparison.ts, 0, 0))
31+
32+
getB(): BB { return this; }
33+
>getB : Symbol(BB.getB, Decl(thisTypeComparison.ts, 9, 21))
34+
>BB : Symbol(BB, Decl(thisTypeComparison.ts, 7, 1))
35+
>this : Symbol(BB, Decl(thisTypeComparison.ts, 7, 1))
36+
}
37+
38+
let dd = new BB();
39+
>dd : Symbol(dd, Decl(thisTypeComparison.ts, 13, 3))
40+
>BB : Symbol(BB, Decl(thisTypeComparison.ts, 7, 1))
41+
42+
dd.do1();
43+
>dd.do1 : Symbol(AA.do1, Decl(thisTypeComparison.ts, 0, 10))
44+
>dd : Symbol(dd, Decl(thisTypeComparison.ts, 13, 3))
45+
>do1 : Symbol(AA.do1, Decl(thisTypeComparison.ts, 0, 10))
46+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//// [tests/cases/compiler/thisTypeComparison.ts] ////
2+
3+
=== thisTypeComparison.ts ===
4+
class AA {
5+
>AA : AA
6+
> : ^^
7+
8+
do1() {
9+
>do1 : () => void
10+
> : ^^^^^^^^^^
11+
12+
const b = dd.getB();
13+
>b : BB
14+
> : ^^
15+
>dd.getB() : BB
16+
> : ^^
17+
>dd.getB : () => BB
18+
> : ^^^^^^
19+
>dd : BB
20+
> : ^^
21+
>getB : () => BB
22+
> : ^^^^^^
23+
24+
if (this === b) {
25+
>this === b : boolean
26+
> : ^^^^^^^
27+
>this : this
28+
> : ^^^^
29+
>b : BB
30+
> : ^^
31+
32+
console.log("this === b");
33+
>console.log("this === b") : void
34+
> : ^^^^
35+
>console.log : (...data: any[]) => void
36+
> : ^^^^ ^^ ^^^^^
37+
>console : Console
38+
> : ^^^^^^^
39+
>log : (...data: any[]) => void
40+
> : ^^^^ ^^ ^^^^^
41+
>"this === b" : "this === b"
42+
> : ^^^^^^^^^^^^
43+
}
44+
}
45+
}
46+
47+
class BB extends AA {
48+
>BB : BB
49+
> : ^^
50+
>AA : AA
51+
> : ^^
52+
53+
getB(): BB { return this; }
54+
>getB : () => BB
55+
> : ^^^^^^
56+
>this : this
57+
> : ^^^^
58+
}
59+
60+
let dd = new BB();
61+
>dd : BB
62+
> : ^^
63+
>new BB() : BB
64+
> : ^^
65+
>BB : typeof BB
66+
> : ^^^^^^^^^
67+
68+
dd.do1();
69+
>dd.do1() : void
70+
> : ^^^^
71+
>dd.do1 : () => void
72+
> : ^^^^^^^^^^
73+
>dd : BB
74+
> : ^^
75+
>do1 : () => void
76+
> : ^^^^^^^^^^
77+
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// @strict: true
2+
3+
// Test 1: Original issue - this === subclass instance should work
4+
class AA {
5+
do1() {
6+
const b = dd.getB();
7+
if (this === b) { // Should not error
8+
console.log("this === b");
9+
}
10+
}
11+
}
12+
13+
class BB extends AA {
14+
getB(): BB { return this; }
15+
}
16+
17+
let dd = new BB();
18+
dd.do1();
19+
20+
// Test 2: this === unrelated class should still error
21+
class CC {
22+
value: number = 42;
23+
}
24+
25+
class DD {
26+
test() {
27+
const c = new CC();
28+
if (this === c) { // Should still error - no relationship
29+
console.log("unrelated");
30+
}
31+
}
32+
}
33+
34+
// Test 3: Multiple inheritance levels
35+
class EE extends BB {
36+
getE(): EE { return this; }
37+
}
38+
39+
class FF extends EE {
40+
testMultiLevel() {
41+
const e = new EE();
42+
if (this === e) { // Should not error - FF extends EE
43+
console.log("multi-level inheritance");
44+
}
45+
}
46+
}
47+
48+
// Test 4: Interface implementation
49+
interface ITest {
50+
getValue(): number;
51+
}
52+
53+
class GG implements ITest {
54+
getValue() { return 42; }
55+
56+
testInterface() {
57+
const impl: ITest = new GG();
58+
if (this === impl) { // Should not error
59+
console.log("interface implementation");
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)