From 16d2eb70759f90ba476a17f20a6e6213c2b2c237 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 28 Apr 2020 12:52:14 -0700 Subject: [PATCH] Error on this.xxx access of previously declared but uninitialized property (#38030) * Error on this.xxx access of previously declared but uninitialized property * Add tests * Accept new baselines --- src/compiler/checker.ts | 2 +- .../abstractPropertyInConstructor.errors.txt | 6 +- ...itializerWithThisPropertyAccess.errors.txt | 44 +++++++ .../initializerWithThisPropertyAccess.js | 114 ++++++++++++++++++ .../initializerWithThisPropertyAccess.symbols | 90 ++++++++++++++ .../initializerWithThisPropertyAccess.types | 97 +++++++++++++++ .../scopeResolutionIdentifiers.errors.txt | 45 +++++++ .../initializerWithThisPropertyAccess.ts | 35 ++++++ 8 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/initializerWithThisPropertyAccess.errors.txt create mode 100644 tests/baselines/reference/initializerWithThisPropertyAccess.js create mode 100644 tests/baselines/reference/initializerWithThisPropertyAccess.symbols create mode 100644 tests/baselines/reference/initializerWithThisPropertyAccess.types create mode 100644 tests/baselines/reference/scopeResolutionIdentifiers.errors.txt create mode 100644 tests/cases/compiler/initializerWithThisPropertyAccess.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b0527ec70b..af3af161ff 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1364,7 +1364,7 @@ namespace ts { return sourceFiles.indexOf(declarationFile) <= sourceFiles.indexOf(useFile); } - if (declaration.pos <= usage.pos) { + if (declaration.pos <= usage.pos && !(isPropertyDeclaration(declaration) && isThisProperty(usage.parent) && !declaration.initializer && !declaration.exclamationToken)) { // declaration is before usage if (declaration.kind === SyntaxKind.BindingElement) { // still might be illegal if declaration and usage are both binding elements (eg var [a = b, b = b] = [1, 2]) diff --git a/tests/baselines/reference/abstractPropertyInConstructor.errors.txt b/tests/baselines/reference/abstractPropertyInConstructor.errors.txt index 869ec3b1e6..030369d81e 100644 --- a/tests/baselines/reference/abstractPropertyInConstructor.errors.txt +++ b/tests/baselines/reference/abstractPropertyInConstructor.errors.txt @@ -2,10 +2,11 @@ tests/cases/compiler/abstractPropertyInConstructor.ts(4,24): error TS2715: Abstr tests/cases/compiler/abstractPropertyInConstructor.ts(7,18): error TS2715: Abstract property 'prop' in class 'AbstractClass' cannot be accessed in the constructor. tests/cases/compiler/abstractPropertyInConstructor.ts(9,14): error TS2715: Abstract property 'cb' in class 'AbstractClass' cannot be accessed in the constructor. tests/cases/compiler/abstractPropertyInConstructor.ts(25,18): error TS2715: Abstract property 'prop' in class 'AbstractClass' cannot be accessed in the constructor. +tests/cases/compiler/abstractPropertyInConstructor.ts(25,18): error TS2729: Property 'prop' is used before its initialization. tests/cases/compiler/abstractPropertyInConstructor.ts(39,22): error TS2715: Abstract property 'prop' in class 'AbstractClass' cannot be accessed in the constructor. -==== tests/cases/compiler/abstractPropertyInConstructor.ts (5 errors) ==== +==== tests/cases/compiler/abstractPropertyInConstructor.ts (6 errors) ==== abstract class AbstractClass { constructor(str: string, other: AbstractClass) { this.method(parseInt(str)); @@ -39,6 +40,9 @@ tests/cases/compiler/abstractPropertyInConstructor.ts(39,22): error TS2715: Abst other = this.prop; ~~~~ !!! error TS2715: Abstract property 'prop' in class 'AbstractClass' cannot be accessed in the constructor. + ~~~~ +!!! error TS2729: Property 'prop' is used before its initialization. +!!! related TS2728 tests/cases/compiler/abstractPropertyInConstructor.ts:20:14: 'prop' is declared here. fn = () => this.prop; method2() { diff --git a/tests/baselines/reference/initializerWithThisPropertyAccess.errors.txt b/tests/baselines/reference/initializerWithThisPropertyAccess.errors.txt new file mode 100644 index 0000000000..0e9502e7cb --- /dev/null +++ b/tests/baselines/reference/initializerWithThisPropertyAccess.errors.txt @@ -0,0 +1,44 @@ +tests/cases/compiler/initializerWithThisPropertyAccess.ts(3,14): error TS2729: Property 'a' is used before its initialization. +tests/cases/compiler/initializerWithThisPropertyAccess.ts(24,29): error TS2729: Property 'bar' is used before its initialization. + + +==== tests/cases/compiler/initializerWithThisPropertyAccess.ts (2 errors) ==== + class A { + a: number; + b = this.a; // Error + ~ +!!! error TS2729: Property 'a' is used before its initialization. +!!! related TS2728 tests/cases/compiler/initializerWithThisPropertyAccess.ts:2:5: 'a' is declared here. + c = () => this.a; + d = (new A()).a; + constructor() { + this.a = 1; + } + } + + class B extends A { + x = this.a; + } + + class C { + a!: number; + b = this.a; + } + + // Repro from #37979 + + class Foo { + private bar: Bar; + readonly barProp = this.bar.prop; + ~~~ +!!! error TS2729: Property 'bar' is used before its initialization. +!!! related TS2728 tests/cases/compiler/initializerWithThisPropertyAccess.ts:23:13: 'bar' is declared here. + constructor() { + this.bar = new Bar(); + } + } + + class Bar { + readonly prop = false; + } + \ No newline at end of file diff --git a/tests/baselines/reference/initializerWithThisPropertyAccess.js b/tests/baselines/reference/initializerWithThisPropertyAccess.js new file mode 100644 index 0000000000..dee3469b39 --- /dev/null +++ b/tests/baselines/reference/initializerWithThisPropertyAccess.js @@ -0,0 +1,114 @@ +//// [initializerWithThisPropertyAccess.ts] +class A { + a: number; + b = this.a; // Error + c = () => this.a; + d = (new A()).a; + constructor() { + this.a = 1; + } +} + +class B extends A { + x = this.a; +} + +class C { + a!: number; + b = this.a; +} + +// Repro from #37979 + +class Foo { + private bar: Bar; + readonly barProp = this.bar.prop; + constructor() { + this.bar = new Bar(); + } +} + +class Bar { + readonly prop = false; +} + + +//// [initializerWithThisPropertyAccess.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 (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var A = /** @class */ (function () { + function A() { + var _this = this; + this.b = this.a; // Error + this.c = function () { return _this.a; }; + this.d = (new A()).a; + this.a = 1; + } + return A; +}()); +var B = /** @class */ (function (_super) { + __extends(B, _super); + function B() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.x = _this.a; + return _this; + } + return B; +}(A)); +var C = /** @class */ (function () { + function C() { + this.b = this.a; + } + return C; +}()); +// Repro from #37979 +var Foo = /** @class */ (function () { + function Foo() { + this.barProp = this.bar.prop; + this.bar = new Bar(); + } + return Foo; +}()); +var Bar = /** @class */ (function () { + function Bar() { + this.prop = false; + } + return Bar; +}()); + + +//// [initializerWithThisPropertyAccess.d.ts] +declare class A { + a: number; + b: number; + c: () => number; + d: number; + constructor(); +} +declare class B extends A { + x: number; +} +declare class C { + a: number; + b: number; +} +declare class Foo { + private bar; + readonly barProp = false; + constructor(); +} +declare class Bar { + readonly prop = false; +} diff --git a/tests/baselines/reference/initializerWithThisPropertyAccess.symbols b/tests/baselines/reference/initializerWithThisPropertyAccess.symbols new file mode 100644 index 0000000000..566e61cb53 --- /dev/null +++ b/tests/baselines/reference/initializerWithThisPropertyAccess.symbols @@ -0,0 +1,90 @@ +=== tests/cases/compiler/initializerWithThisPropertyAccess.ts === +class A { +>A : Symbol(A, Decl(initializerWithThisPropertyAccess.ts, 0, 0)) + + a: number; +>a : Symbol(A.a, Decl(initializerWithThisPropertyAccess.ts, 0, 9)) + + b = this.a; // Error +>b : Symbol(A.b, Decl(initializerWithThisPropertyAccess.ts, 1, 14)) +>this.a : Symbol(A.a, Decl(initializerWithThisPropertyAccess.ts, 0, 9)) +>this : Symbol(A, Decl(initializerWithThisPropertyAccess.ts, 0, 0)) +>a : Symbol(A.a, Decl(initializerWithThisPropertyAccess.ts, 0, 9)) + + c = () => this.a; +>c : Symbol(A.c, Decl(initializerWithThisPropertyAccess.ts, 2, 15)) +>this.a : Symbol(A.a, Decl(initializerWithThisPropertyAccess.ts, 0, 9)) +>this : Symbol(A, Decl(initializerWithThisPropertyAccess.ts, 0, 0)) +>a : Symbol(A.a, Decl(initializerWithThisPropertyAccess.ts, 0, 9)) + + d = (new A()).a; +>d : Symbol(A.d, Decl(initializerWithThisPropertyAccess.ts, 3, 21)) +>(new A()).a : Symbol(A.a, Decl(initializerWithThisPropertyAccess.ts, 0, 9)) +>A : Symbol(A, Decl(initializerWithThisPropertyAccess.ts, 0, 0)) +>a : Symbol(A.a, Decl(initializerWithThisPropertyAccess.ts, 0, 9)) + + constructor() { + this.a = 1; +>this.a : Symbol(A.a, Decl(initializerWithThisPropertyAccess.ts, 0, 9)) +>this : Symbol(A, Decl(initializerWithThisPropertyAccess.ts, 0, 0)) +>a : Symbol(A.a, Decl(initializerWithThisPropertyAccess.ts, 0, 9)) + } +} + +class B extends A { +>B : Symbol(B, Decl(initializerWithThisPropertyAccess.ts, 8, 1)) +>A : Symbol(A, Decl(initializerWithThisPropertyAccess.ts, 0, 0)) + + x = this.a; +>x : Symbol(B.x, Decl(initializerWithThisPropertyAccess.ts, 10, 19)) +>this.a : Symbol(A.a, Decl(initializerWithThisPropertyAccess.ts, 0, 9)) +>this : Symbol(B, Decl(initializerWithThisPropertyAccess.ts, 8, 1)) +>a : Symbol(A.a, Decl(initializerWithThisPropertyAccess.ts, 0, 9)) +} + +class C { +>C : Symbol(C, Decl(initializerWithThisPropertyAccess.ts, 12, 1)) + + a!: number; +>a : Symbol(C.a, Decl(initializerWithThisPropertyAccess.ts, 14, 9)) + + b = this.a; +>b : Symbol(C.b, Decl(initializerWithThisPropertyAccess.ts, 15, 15)) +>this.a : Symbol(C.a, Decl(initializerWithThisPropertyAccess.ts, 14, 9)) +>this : Symbol(C, Decl(initializerWithThisPropertyAccess.ts, 12, 1)) +>a : Symbol(C.a, Decl(initializerWithThisPropertyAccess.ts, 14, 9)) +} + +// Repro from #37979 + +class Foo { +>Foo : Symbol(Foo, Decl(initializerWithThisPropertyAccess.ts, 17, 1)) + + private bar: Bar; +>bar : Symbol(Foo.bar, Decl(initializerWithThisPropertyAccess.ts, 21, 11)) +>Bar : Symbol(Bar, Decl(initializerWithThisPropertyAccess.ts, 27, 1)) + + readonly barProp = this.bar.prop; +>barProp : Symbol(Foo.barProp, Decl(initializerWithThisPropertyAccess.ts, 22, 21)) +>this.bar.prop : Symbol(Bar.prop, Decl(initializerWithThisPropertyAccess.ts, 29, 11)) +>this.bar : Symbol(Foo.bar, Decl(initializerWithThisPropertyAccess.ts, 21, 11)) +>this : Symbol(Foo, Decl(initializerWithThisPropertyAccess.ts, 17, 1)) +>bar : Symbol(Foo.bar, Decl(initializerWithThisPropertyAccess.ts, 21, 11)) +>prop : Symbol(Bar.prop, Decl(initializerWithThisPropertyAccess.ts, 29, 11)) + + constructor() { + this.bar = new Bar(); +>this.bar : Symbol(Foo.bar, Decl(initializerWithThisPropertyAccess.ts, 21, 11)) +>this : Symbol(Foo, Decl(initializerWithThisPropertyAccess.ts, 17, 1)) +>bar : Symbol(Foo.bar, Decl(initializerWithThisPropertyAccess.ts, 21, 11)) +>Bar : Symbol(Bar, Decl(initializerWithThisPropertyAccess.ts, 27, 1)) + } +} + +class Bar { +>Bar : Symbol(Bar, Decl(initializerWithThisPropertyAccess.ts, 27, 1)) + + readonly prop = false; +>prop : Symbol(Bar.prop, Decl(initializerWithThisPropertyAccess.ts, 29, 11)) +} + diff --git a/tests/baselines/reference/initializerWithThisPropertyAccess.types b/tests/baselines/reference/initializerWithThisPropertyAccess.types new file mode 100644 index 0000000000..1abfa0cfe0 --- /dev/null +++ b/tests/baselines/reference/initializerWithThisPropertyAccess.types @@ -0,0 +1,97 @@ +=== tests/cases/compiler/initializerWithThisPropertyAccess.ts === +class A { +>A : A + + a: number; +>a : number + + b = this.a; // Error +>b : number +>this.a : number +>this : this +>a : number + + c = () => this.a; +>c : () => number +>() => this.a : () => number +>this.a : number +>this : this +>a : number + + d = (new A()).a; +>d : number +>(new A()).a : number +>(new A()) : A +>new A() : A +>A : typeof A +>a : number + + constructor() { + this.a = 1; +>this.a = 1 : 1 +>this.a : number +>this : this +>a : number +>1 : 1 + } +} + +class B extends A { +>B : B +>A : A + + x = this.a; +>x : number +>this.a : number +>this : this +>a : number +} + +class C { +>C : C + + a!: number; +>a : number + + b = this.a; +>b : number +>this.a : number +>this : this +>a : number +} + +// Repro from #37979 + +class Foo { +>Foo : Foo + + private bar: Bar; +>bar : Bar + + readonly barProp = this.bar.prop; +>barProp : false +>this.bar.prop : false +>this.bar : Bar +>this : this +>bar : Bar +>prop : false + + constructor() { + this.bar = new Bar(); +>this.bar = new Bar() : Bar +>this.bar : Bar +>this : this +>bar : Bar +>new Bar() : Bar +>Bar : typeof Bar + } +} + +class Bar { +>Bar : Bar + + readonly prop = false; +>prop : false +>false : false +} + diff --git a/tests/baselines/reference/scopeResolutionIdentifiers.errors.txt b/tests/baselines/reference/scopeResolutionIdentifiers.errors.txt new file mode 100644 index 0000000000..f8d0caa00b --- /dev/null +++ b/tests/baselines/reference/scopeResolutionIdentifiers.errors.txt @@ -0,0 +1,45 @@ +tests/cases/conformance/expressions/identifiers/scopeResolutionIdentifiers.ts(24,14): error TS2729: Property 's' is used before its initialization. + + +==== tests/cases/conformance/expressions/identifiers/scopeResolutionIdentifiers.ts (1 errors) ==== + // EveryType used in a nested scope of a different EveryType with the same name, type of the identifier is the one defined in the inner scope + + var s: string; + module M1 { + export var s: number; + var n = s; + var n: number; + } + + module M2 { + var s: number; + var n = s; + var n: number; + } + + function fn() { + var s: boolean; + var n = s; + var n: boolean; + } + + class C { + s: Date; + n = this.s; + ~ +!!! error TS2729: Property 's' is used before its initialization. +!!! related TS2728 tests/cases/conformance/expressions/identifiers/scopeResolutionIdentifiers.ts:23:5: 's' is declared here. + x() { + var p = this.n; + var p: Date; + } + } + + module M3 { + var s: any; + module M4 { + var n = s; + var n: any; + } + } + \ No newline at end of file diff --git a/tests/cases/compiler/initializerWithThisPropertyAccess.ts b/tests/cases/compiler/initializerWithThisPropertyAccess.ts new file mode 100644 index 0000000000..f4a23ecaa9 --- /dev/null +++ b/tests/cases/compiler/initializerWithThisPropertyAccess.ts @@ -0,0 +1,35 @@ +// @strict: true +// @declaration: true + +class A { + a: number; + b = this.a; // Error + c = () => this.a; + d = (new A()).a; + constructor() { + this.a = 1; + } +} + +class B extends A { + x = this.a; +} + +class C { + a!: number; + b = this.a; +} + +// Repro from #37979 + +class Foo { + private bar: Bar; + readonly barProp = this.bar.prop; + constructor() { + this.bar = new Bar(); + } +} + +class Bar { + readonly prop = false; +}