diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 698d75b340..3fe96ca7ac 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1130,6 +1130,10 @@ namespace ts { // still might be illegal if the usage is within a computed property name in the class (eg class A { static p = "a"; [A.p]() {} }) return !findAncestor(usage, n => isComputedPropertyName(n) && n.parent.parent === declaration); } + else if (isPropertyDeclaration(declaration)) { + // still might be illegal if a self-referencing property initializer (eg private x = this.x) + return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage); + } return true; } @@ -1204,6 +1208,40 @@ namespace ts { return false; }); } + + function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration, usage: Node) { + // always legal if usage is after declaration + if (usage.end > declaration.end) { + return false; + } + + // still might be legal if usage is deferred (e.g. x: any = () => this.x) + // otherwise illegal if immediately referenced within the declaration (e.g. x: any = this.x) + const ancestorChangingReferenceScope = findAncestor(usage, (node: Node) => { + if (node === declaration) { + return "quit"; + } + + switch (node.kind) { + case SyntaxKind.ArrowFunction: + case SyntaxKind.PropertyDeclaration: + return true; + case SyntaxKind.Block: + switch (node.parent.kind) { + case SyntaxKind.GetAccessor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.SetAccessor: + return true; + default: + return false; + } + default: + return false; + } + }); + + return ancestorChangingReferenceScope === undefined; + } } /** diff --git a/tests/baselines/reference/classUsedBeforeInitializedVariables.errors.txt b/tests/baselines/reference/classUsedBeforeInitializedVariables.errors.txt new file mode 100644 index 0000000000..685c854039 --- /dev/null +++ b/tests/baselines/reference/classUsedBeforeInitializedVariables.errors.txt @@ -0,0 +1,66 @@ +tests/cases/compiler/classUsedBeforeInitializedVariables.ts(4,15): error TS2729: Property 'p4' is used before its initialization. +tests/cases/compiler/classUsedBeforeInitializedVariables.ts(7,34): error TS2729: Property 'directlyAssigned' is used before its initialization. +tests/cases/compiler/classUsedBeforeInitializedVariables.ts(16,15): error TS2729: Property 'withinObjectLiteral' is used before its initialization. +tests/cases/compiler/classUsedBeforeInitializedVariables.ts(20,19): error TS2729: Property 'withinObjectLiteralGetterName' is used before its initialization. +tests/cases/compiler/classUsedBeforeInitializedVariables.ts(26,19): error TS2729: Property 'withinObjectLiteralSetterName' is used before its initialization. +tests/cases/compiler/classUsedBeforeInitializedVariables.ts(29,64): error TS2729: Property 'withinClassDeclarationExtension' is used before its initialization. + + +==== tests/cases/compiler/classUsedBeforeInitializedVariables.ts (6 errors) ==== + class Test { + p1 = 0; + p2 = this.p1; + p3 = this.p4; + ~~ +!!! error TS2729: Property 'p4' is used before its initialization. +!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:5:5: 'p4' is declared here. + p4 = 0; + + directlyAssigned: any = this.directlyAssigned; + ~~~~~~~~~~~~~~~~ +!!! error TS2729: Property 'directlyAssigned' is used before its initialization. +!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:7:5: 'directlyAssigned' is declared here. + + withinArrowFunction: any = () => this.withinArrowFunction; + + withinFunction: any = function () { + return this.withinFunction; + }; + + withinObjectLiteral: any = { + [this.withinObjectLiteral]: true, + ~~~~~~~~~~~~~~~~~~~ +!!! error TS2729: Property 'withinObjectLiteral' is used before its initialization. +!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:15:5: 'withinObjectLiteral' is declared here. + }; + + withinObjectLiteralGetterName: any = { + get [this.withinObjectLiteralGetterName]() { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2729: Property 'withinObjectLiteralGetterName' is used before its initialization. +!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:19:5: 'withinObjectLiteralGetterName' is declared here. + return true; + } + }; + + withinObjectLiteralSetterName: any = { + set [this.withinObjectLiteralSetterName](_: any) {} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2729: Property 'withinObjectLiteralSetterName' is used before its initialization. +!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:25:5: 'withinObjectLiteralSetterName' is declared here. + }; + + withinClassDeclarationExtension: any = (class extends this.withinClassDeclarationExtension { }); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2729: Property 'withinClassDeclarationExtension' is used before its initialization. +!!! related TS2728 tests/cases/compiler/classUsedBeforeInitializedVariables.ts:29:5: 'withinClassDeclarationExtension' is declared here. + + // These error cases are ignored (not checked by control flow analysis) + + assignedByArrowFunction: any = (() => this.assignedByFunction)(); + + assignedByFunction: any = (function () { + return this.assignedByFunction; + })(); + } + \ No newline at end of file diff --git a/tests/baselines/reference/classUsedBeforeInitializedVariables.js b/tests/baselines/reference/classUsedBeforeInitializedVariables.js new file mode 100644 index 0000000000..3a3591cfda --- /dev/null +++ b/tests/baselines/reference/classUsedBeforeInitializedVariables.js @@ -0,0 +1,102 @@ +//// [classUsedBeforeInitializedVariables.ts] +class Test { + p1 = 0; + p2 = this.p1; + p3 = this.p4; + p4 = 0; + + directlyAssigned: any = this.directlyAssigned; + + withinArrowFunction: any = () => this.withinArrowFunction; + + withinFunction: any = function () { + return this.withinFunction; + }; + + withinObjectLiteral: any = { + [this.withinObjectLiteral]: true, + }; + + withinObjectLiteralGetterName: any = { + get [this.withinObjectLiteralGetterName]() { + return true; + } + }; + + withinObjectLiteralSetterName: any = { + set [this.withinObjectLiteralSetterName](_: any) {} + }; + + withinClassDeclarationExtension: any = (class extends this.withinClassDeclarationExtension { }); + + // These error cases are ignored (not checked by control flow analysis) + + assignedByArrowFunction: any = (() => this.assignedByFunction)(); + + assignedByFunction: any = (function () { + return this.assignedByFunction; + })(); +} + + +//// [classUsedBeforeInitializedVariables.js] +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 Test = /** @class */ (function () { + function Test() { + var _a, _b, _c; + var _this = this; + this.p1 = 0; + this.p2 = this.p1; + this.p3 = this.p4; + this.p4 = 0; + this.directlyAssigned = this.directlyAssigned; + this.withinArrowFunction = function () { return _this.withinArrowFunction; }; + this.withinFunction = function () { + return this.withinFunction; + }; + this.withinObjectLiteral = (_a = {}, + _a[this.withinObjectLiteral] = true, + _a); + this.withinObjectLiteralGetterName = (_b = {}, + Object.defineProperty(_b, this.withinObjectLiteralGetterName, { + get: function () { + return true; + }, + enumerable: true, + configurable: true + }), + _b); + this.withinObjectLiteralSetterName = (_c = {}, + Object.defineProperty(_c, this.withinObjectLiteralSetterName, { + set: function (_) { }, + enumerable: true, + configurable: true + }), + _c); + this.withinClassDeclarationExtension = (/** @class */ (function (_super) { + __extends(class_1, _super); + function class_1() { + return _super !== null && _super.apply(this, arguments) || this; + } + return class_1; + }(this.withinClassDeclarationExtension))); + // These error cases are ignored (not checked by control flow analysis) + this.assignedByArrowFunction = (function () { return _this.assignedByFunction; })(); + this.assignedByFunction = (function () { + return this.assignedByFunction; + })(); + } + return Test; +}()); diff --git a/tests/baselines/reference/classUsedBeforeInitializedVariables.symbols b/tests/baselines/reference/classUsedBeforeInitializedVariables.symbols new file mode 100644 index 0000000000..4885953ed5 --- /dev/null +++ b/tests/baselines/reference/classUsedBeforeInitializedVariables.symbols @@ -0,0 +1,97 @@ +=== tests/cases/compiler/classUsedBeforeInitializedVariables.ts === +class Test { +>Test : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) + + p1 = 0; +>p1 : Symbol(Test.p1, Decl(classUsedBeforeInitializedVariables.ts, 0, 12)) + + p2 = this.p1; +>p2 : Symbol(Test.p2, Decl(classUsedBeforeInitializedVariables.ts, 1, 11)) +>this.p1 : Symbol(Test.p1, Decl(classUsedBeforeInitializedVariables.ts, 0, 12)) +>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) +>p1 : Symbol(Test.p1, Decl(classUsedBeforeInitializedVariables.ts, 0, 12)) + + p3 = this.p4; +>p3 : Symbol(Test.p3, Decl(classUsedBeforeInitializedVariables.ts, 2, 17)) +>this.p4 : Symbol(Test.p4, Decl(classUsedBeforeInitializedVariables.ts, 3, 17)) +>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) +>p4 : Symbol(Test.p4, Decl(classUsedBeforeInitializedVariables.ts, 3, 17)) + + p4 = 0; +>p4 : Symbol(Test.p4, Decl(classUsedBeforeInitializedVariables.ts, 3, 17)) + + directlyAssigned: any = this.directlyAssigned; +>directlyAssigned : Symbol(Test.directlyAssigned, Decl(classUsedBeforeInitializedVariables.ts, 4, 11)) +>this.directlyAssigned : Symbol(Test.directlyAssigned, Decl(classUsedBeforeInitializedVariables.ts, 4, 11)) +>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) +>directlyAssigned : Symbol(Test.directlyAssigned, Decl(classUsedBeforeInitializedVariables.ts, 4, 11)) + + withinArrowFunction: any = () => this.withinArrowFunction; +>withinArrowFunction : Symbol(Test.withinArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 6, 50)) +>this.withinArrowFunction : Symbol(Test.withinArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 6, 50)) +>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) +>withinArrowFunction : Symbol(Test.withinArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 6, 50)) + + withinFunction: any = function () { +>withinFunction : Symbol(Test.withinFunction, Decl(classUsedBeforeInitializedVariables.ts, 8, 62)) + + return this.withinFunction; + }; + + withinObjectLiteral: any = { +>withinObjectLiteral : Symbol(Test.withinObjectLiteral, Decl(classUsedBeforeInitializedVariables.ts, 12, 6)) + + [this.withinObjectLiteral]: true, +>[this.withinObjectLiteral] : Symbol([this.withinObjectLiteral], Decl(classUsedBeforeInitializedVariables.ts, 14, 32)) +>this.withinObjectLiteral : Symbol(Test.withinObjectLiteral, Decl(classUsedBeforeInitializedVariables.ts, 12, 6)) +>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) +>withinObjectLiteral : Symbol(Test.withinObjectLiteral, Decl(classUsedBeforeInitializedVariables.ts, 12, 6)) + + }; + + withinObjectLiteralGetterName: any = { +>withinObjectLiteralGetterName : Symbol(Test.withinObjectLiteralGetterName, Decl(classUsedBeforeInitializedVariables.ts, 16, 6)) + + get [this.withinObjectLiteralGetterName]() { +>[this.withinObjectLiteralGetterName] : Symbol([this.withinObjectLiteralGetterName], Decl(classUsedBeforeInitializedVariables.ts, 18, 42)) +>this.withinObjectLiteralGetterName : Symbol(Test.withinObjectLiteralGetterName, Decl(classUsedBeforeInitializedVariables.ts, 16, 6)) +>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) +>withinObjectLiteralGetterName : Symbol(Test.withinObjectLiteralGetterName, Decl(classUsedBeforeInitializedVariables.ts, 16, 6)) + + return true; + } + }; + + withinObjectLiteralSetterName: any = { +>withinObjectLiteralSetterName : Symbol(Test.withinObjectLiteralSetterName, Decl(classUsedBeforeInitializedVariables.ts, 22, 6)) + + set [this.withinObjectLiteralSetterName](_: any) {} +>[this.withinObjectLiteralSetterName] : Symbol([this.withinObjectLiteralSetterName], Decl(classUsedBeforeInitializedVariables.ts, 24, 42)) +>this.withinObjectLiteralSetterName : Symbol(Test.withinObjectLiteralSetterName, Decl(classUsedBeforeInitializedVariables.ts, 22, 6)) +>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) +>withinObjectLiteralSetterName : Symbol(Test.withinObjectLiteralSetterName, Decl(classUsedBeforeInitializedVariables.ts, 22, 6)) +>_ : Symbol(_, Decl(classUsedBeforeInitializedVariables.ts, 25, 49)) + + }; + + withinClassDeclarationExtension: any = (class extends this.withinClassDeclarationExtension { }); +>withinClassDeclarationExtension : Symbol(Test.withinClassDeclarationExtension, Decl(classUsedBeforeInitializedVariables.ts, 26, 6)) +>this.withinClassDeclarationExtension : Symbol(Test.withinClassDeclarationExtension, Decl(classUsedBeforeInitializedVariables.ts, 26, 6)) +>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) +>withinClassDeclarationExtension : Symbol(Test.withinClassDeclarationExtension, Decl(classUsedBeforeInitializedVariables.ts, 26, 6)) + + // These error cases are ignored (not checked by control flow analysis) + + assignedByArrowFunction: any = (() => this.assignedByFunction)(); +>assignedByArrowFunction : Symbol(Test.assignedByArrowFunction, Decl(classUsedBeforeInitializedVariables.ts, 28, 100)) +>this.assignedByFunction : Symbol(Test.assignedByFunction, Decl(classUsedBeforeInitializedVariables.ts, 32, 69)) +>this : Symbol(Test, Decl(classUsedBeforeInitializedVariables.ts, 0, 0)) +>assignedByFunction : Symbol(Test.assignedByFunction, Decl(classUsedBeforeInitializedVariables.ts, 32, 69)) + + assignedByFunction: any = (function () { +>assignedByFunction : Symbol(Test.assignedByFunction, Decl(classUsedBeforeInitializedVariables.ts, 32, 69)) + + return this.assignedByFunction; + })(); +} + diff --git a/tests/baselines/reference/classUsedBeforeInitializedVariables.types b/tests/baselines/reference/classUsedBeforeInitializedVariables.types new file mode 100644 index 0000000000..f10bf48a8e --- /dev/null +++ b/tests/baselines/reference/classUsedBeforeInitializedVariables.types @@ -0,0 +1,122 @@ +=== tests/cases/compiler/classUsedBeforeInitializedVariables.ts === +class Test { +>Test : Test + + p1 = 0; +>p1 : number +>0 : 0 + + p2 = this.p1; +>p2 : number +>this.p1 : number +>this : this +>p1 : number + + p3 = this.p4; +>p3 : number +>this.p4 : number +>this : this +>p4 : number + + p4 = 0; +>p4 : number +>0 : 0 + + directlyAssigned: any = this.directlyAssigned; +>directlyAssigned : any +>this.directlyAssigned : any +>this : this +>directlyAssigned : any + + withinArrowFunction: any = () => this.withinArrowFunction; +>withinArrowFunction : any +>() => this.withinArrowFunction : () => any +>this.withinArrowFunction : any +>this : this +>withinArrowFunction : any + + withinFunction: any = function () { +>withinFunction : any +>function () { return this.withinFunction; } : () => any + + return this.withinFunction; +>this.withinFunction : any +>this : any +>withinFunction : any + + }; + + withinObjectLiteral: any = { +>withinObjectLiteral : any +>{ [this.withinObjectLiteral]: true, } : { [x: number]: boolean; } + + [this.withinObjectLiteral]: true, +>[this.withinObjectLiteral] : boolean +>this.withinObjectLiteral : any +>this : this +>withinObjectLiteral : any +>true : true + + }; + + withinObjectLiteralGetterName: any = { +>withinObjectLiteralGetterName : any +>{ get [this.withinObjectLiteralGetterName]() { return true; } } : { [x: number]: boolean; } + + get [this.withinObjectLiteralGetterName]() { +>[this.withinObjectLiteralGetterName] : boolean +>this.withinObjectLiteralGetterName : any +>this : this +>withinObjectLiteralGetterName : any + + return true; +>true : true + } + }; + + withinObjectLiteralSetterName: any = { +>withinObjectLiteralSetterName : any +>{ set [this.withinObjectLiteralSetterName](_: any) {} } : { [x: number]: any; } + + set [this.withinObjectLiteralSetterName](_: any) {} +>[this.withinObjectLiteralSetterName] : any +>this.withinObjectLiteralSetterName : any +>this : this +>withinObjectLiteralSetterName : any +>_ : any + + }; + + withinClassDeclarationExtension: any = (class extends this.withinClassDeclarationExtension { }); +>withinClassDeclarationExtension : any +>(class extends this.withinClassDeclarationExtension { }) : typeof (Anonymous class) +>class extends this.withinClassDeclarationExtension { } : typeof (Anonymous class) +>this.withinClassDeclarationExtension : any +>this : this +>withinClassDeclarationExtension : any + + // These error cases are ignored (not checked by control flow analysis) + + assignedByArrowFunction: any = (() => this.assignedByFunction)(); +>assignedByArrowFunction : any +>(() => this.assignedByFunction)() : any +>(() => this.assignedByFunction) : () => any +>() => this.assignedByFunction : () => any +>this.assignedByFunction : any +>this : this +>assignedByFunction : any + + assignedByFunction: any = (function () { +>assignedByFunction : any +>(function () { return this.assignedByFunction; })() : any +>(function () { return this.assignedByFunction; }) : () => any +>function () { return this.assignedByFunction; } : () => any + + return this.assignedByFunction; +>this.assignedByFunction : any +>this : any +>assignedByFunction : any + + })(); +} + diff --git a/tests/baselines/reference/witness.errors.txt b/tests/baselines/reference/witness.errors.txt index e247574118..cf21a8b2ed 100644 --- a/tests/baselines/reference/witness.errors.txt +++ b/tests/baselines/reference/witness.errors.txt @@ -1,4 +1,5 @@ tests/cases/conformance/types/witness/witness.ts(4,21): error TS2372: Parameter 'pInit' cannot be referenced in its initializer. +tests/cases/conformance/types/witness/witness.ts(8,14): error TS2729: Property 'x' is used before its initialization. tests/cases/conformance/types/witness/witness.ts(28,12): error TS2695: Left side of comma operator is unused and has no side effects. tests/cases/conformance/types/witness/witness.ts(29,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'co1' must be of type 'any', but here has type 'number'. tests/cases/conformance/types/witness/witness.ts(30,12): error TS2695: Left side of comma operator is unused and has no side effects. @@ -12,9 +13,11 @@ tests/cases/conformance/types/witness/witness.ts(39,5): error TS2403: Subsequent tests/cases/conformance/types/witness/witness.ts(43,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'cnd1' must be of type 'any', but here has type 'number'. tests/cases/conformance/types/witness/witness.ts(57,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'and1' must be of type 'any', but here has type 'string'. tests/cases/conformance/types/witness/witness.ts(110,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'propAcc1' must be of type 'any', but here has type '{ m: any; }'. +tests/cases/conformance/types/witness/witness.ts(121,14): error TS2729: Property 'n' is used before its initialization. +tests/cases/conformance/types/witness/witness.ts(128,19): error TS2729: Property 'q' is used before its initialization. -==== tests/cases/conformance/types/witness/witness.ts (14 errors) ==== +==== tests/cases/conformance/types/witness/witness.ts (17 errors) ==== // Initializers var varInit = varInit; // any var pInit: any; @@ -25,6 +28,9 @@ tests/cases/conformance/types/witness/witness.ts(110,5): error TS2403: Subsequen } class InitClass { x = this.x; + ~ +!!! error TS2729: Property 'x' is used before its initialization. +!!! related TS2728 tests/cases/conformance/types/witness/witness.ts:8:5: 'x' is declared here. fn() { var y = this.x; var y: any; @@ -164,6 +170,9 @@ tests/cases/conformance/types/witness/witness.ts(110,5): error TS2403: Subsequen // Property access of class instance type class C2 { n = this.n; // n: any + ~ +!!! error TS2729: Property 'n' is used before its initialization. +!!! related TS2728 tests/cases/conformance/types/witness/witness.ts:121:5: 'n' is declared here. } var c2inst = new C2().n; var c2inst: any; @@ -171,6 +180,9 @@ tests/cases/conformance/types/witness/witness.ts(110,5): error TS2403: Subsequen // Constructor function property access class C3 { static q = C3.q; + ~ +!!! error TS2729: Property 'q' is used before its initialization. +!!! related TS2728 tests/cases/conformance/types/witness/witness.ts:128:12: 'q' is declared here. } var qq = C3.q; var qq: any; diff --git a/tests/cases/compiler/classUsedBeforeInitializedVariables.ts b/tests/cases/compiler/classUsedBeforeInitializedVariables.ts new file mode 100644 index 0000000000..f10ae4e20a --- /dev/null +++ b/tests/cases/compiler/classUsedBeforeInitializedVariables.ts @@ -0,0 +1,40 @@ +// @target: es5 + +class Test { + p1 = 0; + p2 = this.p1; + p3 = this.p4; + p4 = 0; + + directlyAssigned: any = this.directlyAssigned; + + withinArrowFunction: any = () => this.withinArrowFunction; + + withinFunction: any = function () { + return this.withinFunction; + }; + + withinObjectLiteral: any = { + [this.withinObjectLiteral]: true, + }; + + withinObjectLiteralGetterName: any = { + get [this.withinObjectLiteralGetterName]() { + return true; + } + }; + + withinObjectLiteralSetterName: any = { + set [this.withinObjectLiteralSetterName](_: any) {} + }; + + withinClassDeclarationExtension: any = (class extends this.withinClassDeclarationExtension { }); + + // These error cases are ignored (not checked by control flow analysis) + + assignedByArrowFunction: any = (() => this.assignedByFunction)(); + + assignedByFunction: any = (function () { + return this.assignedByFunction; + })(); +}