Avoid circular reference in this-property assignments (#37827)
* Avoid circular reference in this-property assignments To do this, don't check this-property assigments that have the this-property of the lhs appearing somewhere on the rhs: ```js class C { m() { this.x = 12 this.x = this.x + this.y } } ``` I tried suppressing the circularity error, but because we cache the first type discovered for a property, this still results in an implicit any for `x` in the previous example. It just doesn't have an error. Fixes #35099 * Add test case + rename function * Use isMatchingReference
This commit is contained in:
parent
795a5c83fe
commit
eb105efdcd
|
@ -7635,6 +7635,9 @@ namespace ts {
|
|||
}
|
||||
return anyType;
|
||||
}
|
||||
if (containsSameNamedThisProperty(expression.left, expression.right)) {
|
||||
return anyType;
|
||||
}
|
||||
const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right));
|
||||
if (type.flags & TypeFlags.Object &&
|
||||
kind === AssignmentDeclarationKind.ModuleExports &&
|
||||
|
@ -7673,6 +7676,12 @@ namespace ts {
|
|||
return type;
|
||||
}
|
||||
|
||||
function containsSameNamedThisProperty(thisProperty: Expression, expression: Expression) {
|
||||
return isPropertyAccessExpression(thisProperty)
|
||||
&& thisProperty.expression.kind === SyntaxKind.ThisKeyword
|
||||
&& forEachChildRecursively(expression, n => isMatchingReference(thisProperty, n));
|
||||
}
|
||||
|
||||
function isDeclarationInConstructor(expression: Expression) {
|
||||
const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false);
|
||||
// Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added.
|
||||
|
|
28
tests/baselines/reference/thisPropertyAssignmentCircular.js
Normal file
28
tests/baselines/reference/thisPropertyAssignmentCircular.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
//// [thisPropertyAssignmentCircular.js]
|
||||
export class Foo {
|
||||
constructor() {
|
||||
this.foo = "Hello";
|
||||
}
|
||||
slicey() {
|
||||
this.foo = this.foo.slice();
|
||||
}
|
||||
m() {
|
||||
this.foo
|
||||
}
|
||||
}
|
||||
|
||||
/** @class */
|
||||
function C() {
|
||||
this.x = 0;
|
||||
this.x = function() { this.x.toString(); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//// [thisPropertyAssignmentCircular.d.ts]
|
||||
export class Foo {
|
||||
foo: string;
|
||||
slicey(): void;
|
||||
m(): void;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
=== tests/cases/conformance/salsa/thisPropertyAssignmentCircular.js ===
|
||||
export class Foo {
|
||||
>Foo : Symbol(Foo, Decl(thisPropertyAssignmentCircular.js, 0, 0))
|
||||
|
||||
constructor() {
|
||||
this.foo = "Hello";
|
||||
>this.foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
|
||||
>this : Symbol(Foo, Decl(thisPropertyAssignmentCircular.js, 0, 0))
|
||||
>foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
|
||||
}
|
||||
slicey() {
|
||||
>slicey : Symbol(Foo.slicey, Decl(thisPropertyAssignmentCircular.js, 3, 5))
|
||||
|
||||
this.foo = this.foo.slice();
|
||||
>this.foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
|
||||
>this : Symbol(Foo, Decl(thisPropertyAssignmentCircular.js, 0, 0))
|
||||
>foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
|
||||
>this.foo.slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --))
|
||||
>this.foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
|
||||
>this : Symbol(Foo, Decl(thisPropertyAssignmentCircular.js, 0, 0))
|
||||
>foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
|
||||
>slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --))
|
||||
}
|
||||
m() {
|
||||
>m : Symbol(Foo.m, Decl(thisPropertyAssignmentCircular.js, 6, 5))
|
||||
|
||||
this.foo
|
||||
>this.foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
|
||||
>this : Symbol(Foo, Decl(thisPropertyAssignmentCircular.js, 0, 0))
|
||||
>foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
|
||||
}
|
||||
}
|
||||
|
||||
/** @class */
|
||||
function C() {
|
||||
>C : Symbol(C, Decl(thisPropertyAssignmentCircular.js, 10, 1))
|
||||
|
||||
this.x = 0;
|
||||
>this.x : Symbol(C.x, Decl(thisPropertyAssignmentCircular.js, 13, 14), Decl(thisPropertyAssignmentCircular.js, 14, 15))
|
||||
>this : Symbol(C, Decl(thisPropertyAssignmentCircular.js, 10, 1))
|
||||
>x : Symbol(C.x, Decl(thisPropertyAssignmentCircular.js, 13, 14), Decl(thisPropertyAssignmentCircular.js, 14, 15))
|
||||
|
||||
this.x = function() { this.x.toString(); }
|
||||
>this.x : Symbol(C.x, Decl(thisPropertyAssignmentCircular.js, 13, 14), Decl(thisPropertyAssignmentCircular.js, 14, 15))
|
||||
>this : Symbol(C, Decl(thisPropertyAssignmentCircular.js, 10, 1))
|
||||
>x : Symbol(C.x, Decl(thisPropertyAssignmentCircular.js, 13, 14), Decl(thisPropertyAssignmentCircular.js, 14, 15))
|
||||
>this.x : Symbol(C.x, Decl(thisPropertyAssignmentCircular.js, 13, 14), Decl(thisPropertyAssignmentCircular.js, 14, 15))
|
||||
>this : Symbol(C, Decl(thisPropertyAssignmentCircular.js, 10, 1))
|
||||
>x : Symbol(C.x, Decl(thisPropertyAssignmentCircular.js, 13, 14), Decl(thisPropertyAssignmentCircular.js, 14, 15))
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
=== tests/cases/conformance/salsa/thisPropertyAssignmentCircular.js ===
|
||||
export class Foo {
|
||||
>Foo : Foo
|
||||
|
||||
constructor() {
|
||||
this.foo = "Hello";
|
||||
>this.foo = "Hello" : "Hello"
|
||||
>this.foo : string
|
||||
>this : this
|
||||
>foo : string
|
||||
>"Hello" : "Hello"
|
||||
}
|
||||
slicey() {
|
||||
>slicey : () => void
|
||||
|
||||
this.foo = this.foo.slice();
|
||||
>this.foo = this.foo.slice() : string
|
||||
>this.foo : string
|
||||
>this : this
|
||||
>foo : string
|
||||
>this.foo.slice() : string
|
||||
>this.foo.slice : (start?: number, end?: number) => string
|
||||
>this.foo : string
|
||||
>this : this
|
||||
>foo : string
|
||||
>slice : (start?: number, end?: number) => string
|
||||
}
|
||||
m() {
|
||||
>m : () => void
|
||||
|
||||
this.foo
|
||||
>this.foo : string
|
||||
>this : this
|
||||
>foo : string
|
||||
}
|
||||
}
|
||||
|
||||
/** @class */
|
||||
function C() {
|
||||
>C : typeof C
|
||||
|
||||
this.x = 0;
|
||||
>this.x = 0 : 0
|
||||
>this.x : any
|
||||
>this : this
|
||||
>x : any
|
||||
>0 : 0
|
||||
|
||||
this.x = function() { this.x.toString(); }
|
||||
>this.x = function() { this.x.toString(); } : () => void
|
||||
>this.x : any
|
||||
>this : this
|
||||
>x : any
|
||||
>function() { this.x.toString(); } : () => void
|
||||
>this.x.toString() : any
|
||||
>this.x.toString : any
|
||||
>this.x : any
|
||||
>this : this
|
||||
>x : any
|
||||
>toString : any
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
// @declaration: true
|
||||
// @emitDeclarationOnly: true
|
||||
// @filename: thisPropertyAssignmentCircular.js
|
||||
export class Foo {
|
||||
constructor() {
|
||||
this.foo = "Hello";
|
||||
}
|
||||
slicey() {
|
||||
this.foo = this.foo.slice();
|
||||
}
|
||||
m() {
|
||||
this.foo
|
||||
}
|
||||
}
|
||||
|
||||
/** @class */
|
||||
function C() {
|
||||
this.x = 0;
|
||||
this.x = function() { this.x.toString(); }
|
||||
}
|
Loading…
Reference in a new issue