Skip to content

Commit b518ea0

Browse files
committed
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
1 parent c47aca0 commit b518ea0

5 files changed

Lines changed: 119 additions & 0 deletions

File tree

src/compiler/checker.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7596,6 +7596,9 @@ namespace ts {
75967596
}
75977597
return anyType;
75987598
}
7599+
if (containsThisProperty(expression.left, expression.right)) {
7600+
return anyType;
7601+
}
75997602
const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right));
76007603
if (type.flags & TypeFlags.Object &&
76017604
kind === AssignmentDeclarationKind.ModuleExports &&
@@ -7634,6 +7637,14 @@ namespace ts {
76347637
return type;
76357638
}
76367639

7640+
function containsThisProperty(thisProperty: Expression, expression: Expression) {
7641+
return isPropertyAccessExpression(thisProperty)
7642+
&& thisProperty.expression.kind === SyntaxKind.ThisKeyword
7643+
&& forEachChildRecursively(
7644+
expression,
7645+
n => isPropertyAccessExpression(n) && n.expression.kind === SyntaxKind.ThisKeyword && n.name.escapedText === thisProperty.name.escapedText);
7646+
}
7647+
76377648
function isDeclarationInConstructor(expression: Expression) {
76387649
const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false);
76397650
// Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//// [thisPropertyAssignmentCircular.js]
2+
export class Foo {
3+
constructor() {
4+
this.foo = "Hello";
5+
}
6+
slicey() {
7+
this.foo = this.foo.slice();
8+
}
9+
m() {
10+
this.foo
11+
}
12+
}
13+
14+
15+
16+
17+
//// [thisPropertyAssignmentCircular.d.ts]
18+
export class Foo {
19+
foo: string;
20+
slicey(): void;
21+
m(): void;
22+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
=== tests/cases/conformance/salsa/thisPropertyAssignmentCircular.js ===
2+
export class Foo {
3+
>Foo : Symbol(Foo, Decl(thisPropertyAssignmentCircular.js, 0, 0))
4+
5+
constructor() {
6+
this.foo = "Hello";
7+
>this.foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
8+
>this : Symbol(Foo, Decl(thisPropertyAssignmentCircular.js, 0, 0))
9+
>foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
10+
}
11+
slicey() {
12+
>slicey : Symbol(Foo.slicey, Decl(thisPropertyAssignmentCircular.js, 3, 5))
13+
14+
this.foo = this.foo.slice();
15+
>this.foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
16+
>this : Symbol(Foo, Decl(thisPropertyAssignmentCircular.js, 0, 0))
17+
>foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
18+
>this.foo.slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --))
19+
>this.foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
20+
>this : Symbol(Foo, Decl(thisPropertyAssignmentCircular.js, 0, 0))
21+
>foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
22+
>slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --))
23+
}
24+
m() {
25+
>m : Symbol(Foo.m, Decl(thisPropertyAssignmentCircular.js, 6, 5))
26+
27+
this.foo
28+
>this.foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
29+
>this : Symbol(Foo, Decl(thisPropertyAssignmentCircular.js, 0, 0))
30+
>foo : Symbol(Foo.foo, Decl(thisPropertyAssignmentCircular.js, 1, 19), Decl(thisPropertyAssignmentCircular.js, 4, 14))
31+
}
32+
}
33+
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
=== tests/cases/conformance/salsa/thisPropertyAssignmentCircular.js ===
2+
export class Foo {
3+
>Foo : Foo
4+
5+
constructor() {
6+
this.foo = "Hello";
7+
>this.foo = "Hello" : "Hello"
8+
>this.foo : string
9+
>this : this
10+
>foo : string
11+
>"Hello" : "Hello"
12+
}
13+
slicey() {
14+
>slicey : () => void
15+
16+
this.foo = this.foo.slice();
17+
>this.foo = this.foo.slice() : string
18+
>this.foo : string
19+
>this : this
20+
>foo : string
21+
>this.foo.slice() : string
22+
>this.foo.slice : (start?: number, end?: number) => string
23+
>this.foo : string
24+
>this : this
25+
>foo : string
26+
>slice : (start?: number, end?: number) => string
27+
}
28+
m() {
29+
>m : () => void
30+
31+
this.foo
32+
>this.foo : string
33+
>this : this
34+
>foo : string
35+
}
36+
}
37+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// @allowJs: true
2+
// @checkJs: true
3+
// @declaration: true
4+
// @emitDeclarationOnly: true
5+
// @filename: thisPropertyAssignmentCircular.js
6+
export class Foo {
7+
constructor() {
8+
this.foo = "Hello";
9+
}
10+
slicey() {
11+
this.foo = this.foo.slice();
12+
}
13+
m() {
14+
this.foo
15+
}
16+
}

0 commit comments

Comments
 (0)