From c82d4a61e39d3ba5fc42618dda92c2375a706b85 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 30 Oct 2015 14:01:06 -0700 Subject: [PATCH 1/6] Forbid 'this' as constructor parameter type --- src/compiler/checker.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d1dbc96695..a1207d023c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4420,12 +4420,19 @@ namespace ts { let container = getThisContainer(node, /*includeArrowFunctions*/ false); let parent = container && container.parent; if (parent && (isClassLike(parent) || parent.kind === SyntaxKind.InterfaceDeclaration)) { - if (!(container.flags & NodeFlags.Static)) { + if (!(container.flags & NodeFlags.Static) && !isConstructorParameter(node, container)) { return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent)).thisType; } } error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); return unknownType; + + function isConstructorParameter(node: TypeNode, container: Node) { + if (container.kind === SyntaxKind.Constructor) { + let ctor = (container); + return !ctor.body.statements.some(st => st === node.parent); + } + } } function getTypeFromThisTypeNode(node: TypeNode): Type { From e609047b786f051aa8dd90e8823b516a41894c36 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 30 Oct 2015 14:02:07 -0700 Subject: [PATCH 2/6] Add tests based on #5449 --- .../reference/thisTypeErrors2.errors.txt | 22 +++++++++++++ tests/baselines/reference/thisTypeErrors2.js | 33 +++++++++++++++++++ .../types/thisType/thisTypeErrors2.ts | 12 +++++++ 3 files changed, 67 insertions(+) create mode 100644 tests/baselines/reference/thisTypeErrors2.errors.txt create mode 100644 tests/baselines/reference/thisTypeErrors2.js create mode 100644 tests/cases/conformance/types/thisType/thisTypeErrors2.ts diff --git a/tests/baselines/reference/thisTypeErrors2.errors.txt b/tests/baselines/reference/thisTypeErrors2.errors.txt new file mode 100644 index 0000000000..cdb49b877d --- /dev/null +++ b/tests/baselines/reference/thisTypeErrors2.errors.txt @@ -0,0 +1,22 @@ +tests/cases/conformance/types/thisType/thisTypeErrors2.ts(2,20): error TS2526: A 'this' type is available only in a non-static member of a class or interface. +tests/cases/conformance/types/thisType/thisTypeErrors2.ts(9,38): error TS2526: A 'this' type is available only in a non-static member of a class or interface. + + +==== tests/cases/conformance/types/thisType/thisTypeErrors2.ts (2 errors) ==== + class Base { + constructor(a: this) { + ~~~~ +!!! error TS2526: A 'this' type is available only in a non-static member of a class or interface. + } + } + class Generic { + } + class Derived { + n: number; + constructor(public host: Generic) { + ~~~~ +!!! error TS2526: A 'this' type is available only in a non-static member of a class or interface. + this.n = 12; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/thisTypeErrors2.js b/tests/baselines/reference/thisTypeErrors2.js new file mode 100644 index 0000000000..476e203999 --- /dev/null +++ b/tests/baselines/reference/thisTypeErrors2.js @@ -0,0 +1,33 @@ +//// [thisTypeErrors2.ts] +class Base { + constructor(a: this) { + } +} +class Generic { +} +class Derived { + n: number; + constructor(public host: Generic) { + this.n = 12; + } +} + + +//// [thisTypeErrors2.js] +var Base = (function () { + function Base(a) { + } + return Base; +})(); +var Generic = (function () { + function Generic() { + } + return Generic; +})(); +var Derived = (function () { + function Derived(host) { + this.host = host; + this.n = 12; + } + return Derived; +})(); diff --git a/tests/cases/conformance/types/thisType/thisTypeErrors2.ts b/tests/cases/conformance/types/thisType/thisTypeErrors2.ts new file mode 100644 index 0000000000..b30a554bd8 --- /dev/null +++ b/tests/cases/conformance/types/thisType/thisTypeErrors2.ts @@ -0,0 +1,12 @@ +class Base { + constructor(a: this) { + } +} +class Generic { +} +class Derived { + n: number; + constructor(public host: Generic) { + this.n = 12; + } +} From 84b894769e33aad84d2bbff209c73db6aa39ab3d Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 30 Oct 2015 14:19:11 -0700 Subject: [PATCH 3/6] Accept baselines --- .../reference/declarationFiles.errors.txt | 5 +- .../reference/thisTypeInClasses.errors.txt | 57 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/thisTypeInClasses.errors.txt diff --git a/tests/baselines/reference/declarationFiles.errors.txt b/tests/baselines/reference/declarationFiles.errors.txt index c55cac9000..bbd65b6d65 100644 --- a/tests/baselines/reference/declarationFiles.errors.txt +++ b/tests/baselines/reference/declarationFiles.errors.txt @@ -1,15 +1,18 @@ +tests/cases/conformance/types/thisType/declarationFiles.ts(5,20): error TS2526: A 'this' type is available only in a non-static member of a class or interface. tests/cases/conformance/types/thisType/declarationFiles.ts(31,5): error TS2527: The inferred type of 'x1' references an inaccessible 'this' type. A type annotation is necessary. tests/cases/conformance/types/thisType/declarationFiles.ts(33,5): error TS2527: The inferred type of 'x3' references an inaccessible 'this' type. A type annotation is necessary. tests/cases/conformance/types/thisType/declarationFiles.ts(35,5): error TS2527: The inferred type of 'f1' references an inaccessible 'this' type. A type annotation is necessary. tests/cases/conformance/types/thisType/declarationFiles.ts(41,5): error TS2527: The inferred type of 'f3' references an inaccessible 'this' type. A type annotation is necessary. -==== tests/cases/conformance/types/thisType/declarationFiles.ts (4 errors) ==== +==== tests/cases/conformance/types/thisType/declarationFiles.ts (5 errors) ==== class C1 { x: this; f(x: this): this { return undefined; } constructor(x: this) { } + ~~~~ +!!! error TS2526: A 'this' type is available only in a non-static member of a class or interface. } class C2 { diff --git a/tests/baselines/reference/thisTypeInClasses.errors.txt b/tests/baselines/reference/thisTypeInClasses.errors.txt new file mode 100644 index 0000000000..db2ae1b579 --- /dev/null +++ b/tests/baselines/reference/thisTypeInClasses.errors.txt @@ -0,0 +1,57 @@ +tests/cases/conformance/types/thisType/thisTypeInClasses.ts(4,20): error TS2526: A 'this' type is available only in a non-static member of a class or interface. + + +==== tests/cases/conformance/types/thisType/thisTypeInClasses.ts (1 errors) ==== + class C1 { + x: this; + f(x: this): this { return undefined; } + constructor(x: this) { } + ~~~~ +!!! error TS2526: A 'this' type is available only in a non-static member of a class or interface. + } + + class C2 { + [x: string]: this; + } + + interface Foo { + x: T; + y: this; + } + + class C3 { + a: this[]; + b: [this, this]; + c: this | Date; + d: this & Date; + e: (((this))); + f: (x: this) => this; + g: new (x: this) => this; + h: Foo; + i: Foo this)>; + j: (x: any) => x is this; + } + + declare class C4 { + x: this; + f(x: this): this; + } + + class C5 { + foo() { + let f1 = (x: this): this => this; + let f2 = (x: this) => this; + let f3 = (x: this) => (y: this) => this; + let f4 = (x: this) => { + let g = (y: this) => { + return () => this; + } + return g(this); + } + } + bar() { + let x1 = undefined; + let x2 = undefined as this; + } + } + \ No newline at end of file From 6aecd43d9a7edca358df3e0ef629e0b2d994c6d0 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 30 Oct 2015 15:03:44 -0700 Subject: [PATCH 4/6] Fix `isConstructorParameter` --- src/compiler/checker.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a1207d023c..6ea8d89632 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4427,10 +4427,17 @@ namespace ts { error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); return unknownType; - function isConstructorParameter(node: TypeNode, container: Node) { + function isConstructorParameter(node: Node, container: Node) { if (container.kind === SyntaxKind.Constructor) { let ctor = (container); - return !ctor.body.statements.some(st => st === node.parent); + while (node && node !== ctor) { + if (node === ctor.body) { + return false; + } + node = node.parent; + } + + return true; } } } From 201266b97fa30087f13a84ab90fc647e23e41389 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Sat, 31 Oct 2015 12:42:04 -0700 Subject: [PATCH 5/6] Switch to `isNodeDescendantOf` --- src/compiler/checker.ts | 17 ++--------------- src/compiler/emitter.ts | 8 -------- src/compiler/utilities.ts | 8 ++++++++ 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6ea8d89632..80bc58217a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4420,26 +4420,13 @@ namespace ts { let container = getThisContainer(node, /*includeArrowFunctions*/ false); let parent = container && container.parent; if (parent && (isClassLike(parent) || parent.kind === SyntaxKind.InterfaceDeclaration)) { - if (!(container.flags & NodeFlags.Static) && !isConstructorParameter(node, container)) { + if (!(container.flags & NodeFlags.Static) && + (container.kind !== SyntaxKind.Constructor || isNodeDescendentOf(node, (container).body))) { return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent)).thisType; } } error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); return unknownType; - - function isConstructorParameter(node: Node, container: Node) { - if (container.kind === SyntaxKind.Constructor) { - let ctor = (container); - while (node && node !== ctor) { - if (node === ctor.body) { - return false; - } - node = node.parent; - } - - return true; - } - } } function getTypeFromThisTypeNode(node: TypeNode): Type { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index b2fa553a73..abcea979dd 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -359,14 +359,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi sourceMaps: sourceMapDataList }; - function isNodeDescendentOf(node: Node, ancestor: Node): boolean { - while (node) { - if (node === ancestor) return true; - node = node.parent; - } - return false; - } - function isUniqueLocalName(name: string, container: Node): boolean { for (let node = container; isNodeDescendentOf(node, container); node = node.nextContainer) { if (node.locals && hasProperty(node.locals, name)) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index fd991039d3..9b8948d6cc 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1167,6 +1167,14 @@ namespace ts { return !!node && (node.kind === SyntaxKind.ArrayBindingPattern || node.kind === SyntaxKind.ObjectBindingPattern); } + export function isNodeDescendentOf(node: Node, ancestor: Node): boolean { + while (node) { + if (node === ancestor) return true; + node = node.parent; + } + return false; + } + export function isInAmbientContext(node: Node): boolean { while (node) { if (node.flags & (NodeFlags.Ambient | NodeFlags.DeclarationFile)) { From 67b9647069322eb3a29f8926500d834b89b0b5ee Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 2 Nov 2015 10:47:56 -0800 Subject: [PATCH 6/6] Add a variable of type `this` in constructor body The test already had a reference to the `this` value, but that doesn't show that the *type* is allowed. --- tests/baselines/reference/thisTypeErrors2.errors.txt | 1 + tests/baselines/reference/thisTypeErrors2.js | 2 ++ tests/cases/conformance/types/thisType/thisTypeErrors2.ts | 1 + 3 files changed, 4 insertions(+) diff --git a/tests/baselines/reference/thisTypeErrors2.errors.txt b/tests/baselines/reference/thisTypeErrors2.errors.txt index cdb49b877d..783325f4ef 100644 --- a/tests/baselines/reference/thisTypeErrors2.errors.txt +++ b/tests/baselines/reference/thisTypeErrors2.errors.txt @@ -16,6 +16,7 @@ tests/cases/conformance/types/thisType/thisTypeErrors2.ts(9,38): error TS2526: A constructor(public host: Generic) { ~~~~ !!! error TS2526: A 'this' type is available only in a non-static member of a class or interface. + let self: this = this; this.n = 12; } } diff --git a/tests/baselines/reference/thisTypeErrors2.js b/tests/baselines/reference/thisTypeErrors2.js index 476e203999..f48d0874e5 100644 --- a/tests/baselines/reference/thisTypeErrors2.js +++ b/tests/baselines/reference/thisTypeErrors2.js @@ -8,6 +8,7 @@ class Generic { class Derived { n: number; constructor(public host: Generic) { + let self: this = this; this.n = 12; } } @@ -27,6 +28,7 @@ var Generic = (function () { var Derived = (function () { function Derived(host) { this.host = host; + var self = this; this.n = 12; } return Derived; diff --git a/tests/cases/conformance/types/thisType/thisTypeErrors2.ts b/tests/cases/conformance/types/thisType/thisTypeErrors2.ts index b30a554bd8..d29e714c4d 100644 --- a/tests/cases/conformance/types/thisType/thisTypeErrors2.ts +++ b/tests/cases/conformance/types/thisType/thisTypeErrors2.ts @@ -7,6 +7,7 @@ class Generic { class Derived { n: number; constructor(public host: Generic) { + let self: this = this; this.n = 12; } }