mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 21:53:42 -06:00
Implement fix for this type comparison false positive
Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
This commit is contained in:
parent
6355df5d95
commit
c3df353a8d
@ -22143,11 +22143,26 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
(source as NumberLiteralType).value === (target as NumberLiteralType).value
|
||||
) return true;
|
||||
if (s & TypeFlags.BigIntLike && t & TypeFlags.BigInt) return true;
|
||||
if (s & TypeFlags.BooleanLike && t & TypeFlags.Boolean) return true;
|
||||
if (s & TypeFlags.ESSymbolLike && t & TypeFlags.ESSymbol) return true;
|
||||
if (
|
||||
s & TypeFlags.Enum && t & TypeFlags.Enum && source.symbol.escapedName === target.symbol.escapedName &&
|
||||
isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)
|
||||
if (s & TypeFlags.BooleanLike && t & TypeFlags.Boolean) return true;
|
||||
if (s & TypeFlags.ESSymbolLike && t & TypeFlags.ESSymbol) return true;
|
||||
// For comparable relation, revert `this` type parameters back to their constrained class type
|
||||
if (relation === comparableRelation) {
|
||||
if (s & TypeFlags.TypeParameter && (source as TypeParameter).isThisType) {
|
||||
const constraint = getConstraintOfTypeParameter(source as TypeParameter);
|
||||
if (constraint && isTypeRelatedTo(constraint, target, relation)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (t & TypeFlags.TypeParameter && (target as TypeParameter).isThisType) {
|
||||
const constraint = getConstraintOfTypeParameter(target as TypeParameter);
|
||||
if (constraint && isTypeRelatedTo(source, constraint, relation)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
s & TypeFlags.Enum && t & TypeFlags.Enum && source.symbol.escapedName === target.symbol.escapedName &&
|
||||
isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)
|
||||
) return true;
|
||||
if (s & TypeFlags.EnumLiteral && t & TypeFlags.EnumLiteral) {
|
||||
if (s & TypeFlags.Union && t & TypeFlags.Union && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true;
|
||||
|
||||
57
tests/baselines/reference/thisTypeComparison.js
Normal file
57
tests/baselines/reference/thisTypeComparison.js
Normal file
@ -0,0 +1,57 @@
|
||||
//// [tests/cases/compiler/thisTypeComparison.ts] ////
|
||||
|
||||
//// [thisTypeComparison.ts]
|
||||
class AA {
|
||||
do1() {
|
||||
const b = dd.getB();
|
||||
if (this === b) {
|
||||
console.log("this === b");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BB extends AA {
|
||||
getB(): BB { return this; }
|
||||
}
|
||||
|
||||
let dd = new BB();
|
||||
dd.do1();
|
||||
|
||||
//// [thisTypeComparison.js]
|
||||
"use strict";
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
if (typeof b !== "function" && b !== null)
|
||||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
var AA = /** @class */ (function () {
|
||||
function AA() {
|
||||
}
|
||||
AA.prototype.do1 = function () {
|
||||
var b = dd.getB();
|
||||
if (this === b) {
|
||||
console.log("this === b");
|
||||
}
|
||||
};
|
||||
return AA;
|
||||
}());
|
||||
var BB = /** @class */ (function (_super) {
|
||||
__extends(BB, _super);
|
||||
function BB() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
BB.prototype.getB = function () { return this; };
|
||||
return BB;
|
||||
}(AA));
|
||||
var dd = new BB();
|
||||
dd.do1();
|
||||
46
tests/baselines/reference/thisTypeComparison.symbols
Normal file
46
tests/baselines/reference/thisTypeComparison.symbols
Normal file
@ -0,0 +1,46 @@
|
||||
//// [tests/cases/compiler/thisTypeComparison.ts] ////
|
||||
|
||||
=== thisTypeComparison.ts ===
|
||||
class AA {
|
||||
>AA : Symbol(AA, Decl(thisTypeComparison.ts, 0, 0))
|
||||
|
||||
do1() {
|
||||
>do1 : Symbol(AA.do1, Decl(thisTypeComparison.ts, 0, 10))
|
||||
|
||||
const b = dd.getB();
|
||||
>b : Symbol(b, Decl(thisTypeComparison.ts, 2, 13))
|
||||
>dd.getB : Symbol(BB.getB, Decl(thisTypeComparison.ts, 9, 21))
|
||||
>dd : Symbol(dd, Decl(thisTypeComparison.ts, 13, 3))
|
||||
>getB : Symbol(BB.getB, Decl(thisTypeComparison.ts, 9, 21))
|
||||
|
||||
if (this === b) {
|
||||
>this : Symbol(AA, Decl(thisTypeComparison.ts, 0, 0))
|
||||
>b : Symbol(b, Decl(thisTypeComparison.ts, 2, 13))
|
||||
|
||||
console.log("this === b");
|
||||
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
|
||||
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
|
||||
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BB extends AA {
|
||||
>BB : Symbol(BB, Decl(thisTypeComparison.ts, 7, 1))
|
||||
>AA : Symbol(AA, Decl(thisTypeComparison.ts, 0, 0))
|
||||
|
||||
getB(): BB { return this; }
|
||||
>getB : Symbol(BB.getB, Decl(thisTypeComparison.ts, 9, 21))
|
||||
>BB : Symbol(BB, Decl(thisTypeComparison.ts, 7, 1))
|
||||
>this : Symbol(BB, Decl(thisTypeComparison.ts, 7, 1))
|
||||
}
|
||||
|
||||
let dd = new BB();
|
||||
>dd : Symbol(dd, Decl(thisTypeComparison.ts, 13, 3))
|
||||
>BB : Symbol(BB, Decl(thisTypeComparison.ts, 7, 1))
|
||||
|
||||
dd.do1();
|
||||
>dd.do1 : Symbol(AA.do1, Decl(thisTypeComparison.ts, 0, 10))
|
||||
>dd : Symbol(dd, Decl(thisTypeComparison.ts, 13, 3))
|
||||
>do1 : Symbol(AA.do1, Decl(thisTypeComparison.ts, 0, 10))
|
||||
|
||||
77
tests/baselines/reference/thisTypeComparison.types
Normal file
77
tests/baselines/reference/thisTypeComparison.types
Normal file
@ -0,0 +1,77 @@
|
||||
//// [tests/cases/compiler/thisTypeComparison.ts] ////
|
||||
|
||||
=== thisTypeComparison.ts ===
|
||||
class AA {
|
||||
>AA : AA
|
||||
> : ^^
|
||||
|
||||
do1() {
|
||||
>do1 : () => void
|
||||
> : ^^^^^^^^^^
|
||||
|
||||
const b = dd.getB();
|
||||
>b : BB
|
||||
> : ^^
|
||||
>dd.getB() : BB
|
||||
> : ^^
|
||||
>dd.getB : () => BB
|
||||
> : ^^^^^^
|
||||
>dd : BB
|
||||
> : ^^
|
||||
>getB : () => BB
|
||||
> : ^^^^^^
|
||||
|
||||
if (this === b) {
|
||||
>this === b : boolean
|
||||
> : ^^^^^^^
|
||||
>this : this
|
||||
> : ^^^^
|
||||
>b : BB
|
||||
> : ^^
|
||||
|
||||
console.log("this === b");
|
||||
>console.log("this === b") : void
|
||||
> : ^^^^
|
||||
>console.log : (...data: any[]) => void
|
||||
> : ^^^^ ^^ ^^^^^
|
||||
>console : Console
|
||||
> : ^^^^^^^
|
||||
>log : (...data: any[]) => void
|
||||
> : ^^^^ ^^ ^^^^^
|
||||
>"this === b" : "this === b"
|
||||
> : ^^^^^^^^^^^^
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BB extends AA {
|
||||
>BB : BB
|
||||
> : ^^
|
||||
>AA : AA
|
||||
> : ^^
|
||||
|
||||
getB(): BB { return this; }
|
||||
>getB : () => BB
|
||||
> : ^^^^^^
|
||||
>this : this
|
||||
> : ^^^^
|
||||
}
|
||||
|
||||
let dd = new BB();
|
||||
>dd : BB
|
||||
> : ^^
|
||||
>new BB() : BB
|
||||
> : ^^
|
||||
>BB : typeof BB
|
||||
> : ^^^^^^^^^
|
||||
|
||||
dd.do1();
|
||||
>dd.do1() : void
|
||||
> : ^^^^
|
||||
>dd.do1 : () => void
|
||||
> : ^^^^^^^^^^
|
||||
>dd : BB
|
||||
> : ^^
|
||||
>do1 : () => void
|
||||
> : ^^^^^^^^^^
|
||||
|
||||
62
tests/cases/compiler/thisTypeComparisonExtended.ts
Normal file
62
tests/cases/compiler/thisTypeComparisonExtended.ts
Normal file
@ -0,0 +1,62 @@
|
||||
// @strict: true
|
||||
|
||||
// Test 1: Original issue - this === subclass instance should work
|
||||
class AA {
|
||||
do1() {
|
||||
const b = dd.getB();
|
||||
if (this === b) { // Should not error
|
||||
console.log("this === b");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BB extends AA {
|
||||
getB(): BB { return this; }
|
||||
}
|
||||
|
||||
let dd = new BB();
|
||||
dd.do1();
|
||||
|
||||
// Test 2: this === unrelated class should still error
|
||||
class CC {
|
||||
value: number = 42;
|
||||
}
|
||||
|
||||
class DD {
|
||||
test() {
|
||||
const c = new CC();
|
||||
if (this === c) { // Should still error - no relationship
|
||||
console.log("unrelated");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test 3: Multiple inheritance levels
|
||||
class EE extends BB {
|
||||
getE(): EE { return this; }
|
||||
}
|
||||
|
||||
class FF extends EE {
|
||||
testMultiLevel() {
|
||||
const e = new EE();
|
||||
if (this === e) { // Should not error - FF extends EE
|
||||
console.log("multi-level inheritance");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test 4: Interface implementation
|
||||
interface ITest {
|
||||
getValue(): number;
|
||||
}
|
||||
|
||||
class GG implements ITest {
|
||||
getValue() { return 42; }
|
||||
|
||||
testInterface() {
|
||||
const impl: ITest = new GG();
|
||||
if (this === impl) { // Should not error
|
||||
console.log("interface implementation");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user