Skip to content

Commit 53a756e

Browse files
authored
Type this in more constructor functions (#39447)
* Type `this` in more constructor functions Previously, `this: this` in constructor functions only when there was an explicit `@constructor` tag on the function. Now, `this: this` for any function that's known to be a constructor function. This improves completions inside constructor functions; also note that previously the compiler *did* type `this: this` inside methods of constructor functions, so this fix makes us more consistent. This is reflected in the large number of baselines that improve. The fix is a simple switch to `isJSConstructor`, which is the standard way to detect constructor functions. I'm not sure why the original PR didn't use this method. I remember discussing this limitation in the original bug, #25979, and I guess I decided that it made sense. But I was heavily primed by the bug's framing of the problem in terms of `noImplicitThis`, which *should* require an explicit `@constructor` tag. With better typing comes better detection of `@readonly` assignment; I had to fix the readonly detection code to use `isJSConstructor` as well. * Remove `Add @Class tag` fix for noImplicitThis. The new rules mean that it never applies. It's possible that it should apply to functions like ```js function f() { this.init() } ``` In which `init` is never defined, but I think this program is incomplete enough that not offering the fix is fine. * Fix precedence of `@this` Previously, both `@class` and `@this` in a jsdoc would cause the `@this` annotation to be ignored. This became a worse problem with this PR, because `this` is correctly typed even without the annotation. This commit makes sure that `@this` is checked first and used if present.
1 parent 030f5e8 commit 53a756e

File tree

104 files changed

+376
-215
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+376
-215
lines changed

src/compiler/checker.ts

+12-25
Original file line numberDiff line numberDiff line change
@@ -22440,30 +22440,23 @@ namespace ts {
2244022440
const isInJS = isInJSFile(node);
2244122441
if (isFunctionLike(container) &&
2244222442
(!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) {
22443+
let thisType = getThisTypeOfDeclaration(container) || isInJS && getTypeForThisExpressionFromJSDoc(container);
2244322444
// Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated.
2244422445
// If this is a function in a JS file, it might be a class method.
22445-
const className = getClassNameFromPrototypeMethod(container);
22446-
if (isInJS && className) {
22447-
const classSymbol = checkExpression(className).symbol;
22448-
if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) {
22449-
const classType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType;
22450-
if (classType) {
22451-
return getFlowTypeOfReference(node, classType);
22446+
if (!thisType) {
22447+
const className = getClassNameFromPrototypeMethod(container);
22448+
if (isInJS && className) {
22449+
const classSymbol = checkExpression(className).symbol;
22450+
if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) {
22451+
thisType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType;
2245222452
}
2245322453
}
22454-
}
22455-
// Check if it's a constructor definition, can be either a variable decl or function decl
22456-
// i.e.
22457-
// * /** @constructor */ function [name]() { ... }
22458-
// * /** @constructor */ var x = function() { ... }
22459-
else if (isInJS &&
22460-
(container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) &&
22461-
getJSDocClassTag(container)) {
22462-
const classType = (getDeclaredTypeOfSymbol(getMergedSymbol(container.symbol)) as InterfaceType).thisType!;
22463-
return getFlowTypeOfReference(node, classType);
22454+
else if (isJSConstructor(container)) {
22455+
thisType = (getDeclaredTypeOfSymbol(getMergedSymbol(container.symbol)) as InterfaceType).thisType;
22456+
}
22457+
thisType ||= getContextualThisParameterType(container);
2246422458
}
2246522459

22466-
const thisType = getThisTypeOfDeclaration(container) || getContextualThisParameterType(container);
2246722460
if (thisType) {
2246822461
return getFlowTypeOfReference(node, thisType);
2246922462
}
@@ -22475,12 +22468,6 @@ namespace ts {
2247522468
return getFlowTypeOfReference(node, type);
2247622469
}
2247722470

22478-
if (isInJS) {
22479-
const type = getTypeForThisExpressionFromJSDoc(container);
22480-
if (type && type !== errorType) {
22481-
return getFlowTypeOfReference(node, type);
22482-
}
22483-
}
2248422471
if (isSourceFile(container)) {
2248522472
// look up in the source file's locals or exports
2248622473
if (container.commonJsModuleIndicator) {
@@ -28574,7 +28561,7 @@ namespace ts {
2857428561
expr.expression.kind === SyntaxKind.ThisKeyword) {
2857528562
// Look for if this is the constructor for the class that `symbol` is a property of.
2857628563
const ctor = getContainingFunction(expr);
28577-
if (!(ctor && ctor.kind === SyntaxKind.Constructor)) {
28564+
if (!(ctor && (ctor.kind === SyntaxKind.Constructor || isJSConstructor(ctor)))) {
2857828565
return true;
2857928566
}
2858028567
if (symbol.valueDeclaration) {

src/services/codefixes/fixImplicitThis.ts

-5
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,5 @@ namespace ts.codefix {
5252
return [Diagnostics.Convert_function_declaration_0_to_arrow_function, name!.text];
5353
}
5454
}
55-
// No outer 'this', add a @class tag if in a JS constructor function
56-
else if (isSourceFileJS(sourceFile) && isPropertyAccessExpression(token.parent) && isAssignmentExpression(token.parent.parent)) {
57-
addJSDocTags(changes, sourceFile, fn, [factory.createJSDocClassTag(/*tagName*/ undefined)]);
58-
return Diagnostics.Add_class_tag;
59-
}
6055
}
6156
}

tests/baselines/reference/assignmentToVoidZero2.errors.txt

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
tests/cases/conformance/salsa/assignmentToVoidZero2.js(2,9): error TS2339: Property 'k' does not exist on type 'typeof import("tests/cases/conformance/salsa/assignmentToVoidZero2")'.
22
tests/cases/conformance/salsa/assignmentToVoidZero2.js(5,3): error TS2339: Property 'y' does not exist on type 'typeof o'.
33
tests/cases/conformance/salsa/assignmentToVoidZero2.js(6,9): error TS2339: Property 'y' does not exist on type 'typeof o'.
4+
tests/cases/conformance/salsa/assignmentToVoidZero2.js(10,10): error TS2339: Property 'q' does not exist on type 'C'.
45
tests/cases/conformance/salsa/assignmentToVoidZero2.js(13,9): error TS2339: Property 'q' does not exist on type 'C'.
56
tests/cases/conformance/salsa/importer.js(1,13): error TS2305: Module '"./assignmentToVoidZero2"' has no exported member 'k'.
67

78

8-
==== tests/cases/conformance/salsa/assignmentToVoidZero2.js (4 errors) ====
9+
==== tests/cases/conformance/salsa/assignmentToVoidZero2.js (5 errors) ====
910
exports.j = 1;
1011
exports.k = void 0;
1112
~
@@ -22,6 +23,8 @@ tests/cases/conformance/salsa/importer.js(1,13): error TS2305: Module '"./assign
2223
function C() {
2324
this.p = 1
2425
this.q = void 0
26+
~
27+
!!! error TS2339: Property 'q' does not exist on type 'C'.
2528
}
2629
var c = new C()
2730
c.p + c.q

tests/baselines/reference/assignmentToVoidZero2.symbols

+3
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@ function C() {
2828
>C : Symbol(C, Decl(assignmentToVoidZero2.js, 5, 9))
2929

3030
this.p = 1
31+
>this.p : Symbol(C.p, Decl(assignmentToVoidZero2.js, 7, 14))
32+
>this : Symbol(C, Decl(assignmentToVoidZero2.js, 5, 9))
3133
>p : Symbol(C.p, Decl(assignmentToVoidZero2.js, 7, 14))
3234

3335
this.q = void 0
36+
>this : Symbol(C, Decl(assignmentToVoidZero2.js, 5, 9))
3437
}
3538
var c = new C()
3639
>c : Symbol(c, Decl(assignmentToVoidZero2.js, 11, 3))

tests/baselines/reference/assignmentToVoidZero2.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ function C() {
4848
this.p = 1
4949
>this.p = 1 : 1
5050
>this.p : any
51-
>this : any
51+
>this : this
5252
>p : any
5353
>1 : 1
5454

5555
this.q = void 0
5656
>this.q = void 0 : undefined
5757
>this.q : any
58-
>this : any
58+
>this : this
5959
>q : any
6060
>void 0 : undefined
6161
>0 : 0

tests/baselines/reference/callbackCrossModule.symbols

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ function C() {
1313
>C : Symbol(C, Decl(mod1.js, 4, 18))
1414

1515
this.p = 1
16+
>this.p : Symbol(C.p, Decl(mod1.js, 5, 14))
17+
>this : Symbol(C, Decl(mod1.js, 4, 18))
1618
>p : Symbol(C.p, Decl(mod1.js, 5, 14))
1719
}
1820

tests/baselines/reference/callbackCrossModule.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ function C() {
1616
this.p = 1
1717
>this.p = 1 : 1
1818
>this.p : any
19-
>this : any
19+
>this : this
2020
>p : any
2121
>1 : 1
2222
}

tests/baselines/reference/chainedPrototypeAssignment.symbols

+4
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,17 @@ var A = function A() {
4242
>A : Symbol(A, Decl(mod.js, 1, 7))
4343

4444
this.a = 1
45+
>this.a : Symbol(A.a, Decl(mod.js, 1, 22))
46+
>this : Symbol(A, Decl(mod.js, 1, 7))
4547
>a : Symbol(A.a, Decl(mod.js, 1, 22))
4648
}
4749
var B = function B() {
4850
>B : Symbol(B, Decl(mod.js, 4, 3), Decl(mod.js, 9, 13))
4951
>B : Symbol(B, Decl(mod.js, 4, 7))
5052

5153
this.b = 2
54+
>this.b : Symbol(B.b, Decl(mod.js, 4, 22))
55+
>this : Symbol(B, Decl(mod.js, 4, 7))
5256
>b : Symbol(B.b, Decl(mod.js, 4, 22))
5357
}
5458
exports.A = A

tests/baselines/reference/chainedPrototypeAssignment.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ var A = function A() {
5252
this.a = 1
5353
>this.a = 1 : 1
5454
>this.a : any
55-
>this : any
55+
>this : this
5656
>a : any
5757
>1 : 1
5858
}
@@ -64,7 +64,7 @@ var B = function B() {
6464
this.b = 2
6565
>this.b = 2 : 2
6666
>this.b : any
67-
>this : any
67+
>this : this
6868
>b : any
6969
>2 : 2
7070
}

tests/baselines/reference/classCanExtendConstructorFunction.symbols

+2
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ function Soup(flavour) {
193193
>flavour : Symbol(flavour, Decl(generic.js, 4, 14))
194194

195195
this.flavour = flavour
196+
>this.flavour : Symbol(Soup.flavour, Decl(generic.js, 4, 24))
197+
>this : Symbol(Soup, Decl(generic.js, 0, 0))
196198
>flavour : Symbol(Soup.flavour, Decl(generic.js, 4, 24))
197199
>flavour : Symbol(flavour, Decl(generic.js, 4, 14))
198200
}

tests/baselines/reference/classCanExtendConstructorFunction.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ function Soup(flavour) {
238238
this.flavour = flavour
239239
>this.flavour = flavour : T
240240
>this.flavour : any
241-
>this : any
241+
>this : this
242242
>flavour : any
243243
>flavour : T
244244
}

tests/baselines/reference/commonjsAccessExports.symbols

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ exports.x;
1818
>Cls : Symbol(Cls, Decl(a.js, 4, 1))
1919

2020
this.x = 0;
21+
>this.x : Symbol(Cls.x, Decl(a.js, 6, 30))
22+
>this : Symbol(Cls, Decl(a.js, 6, 17))
2123
>x : Symbol(Cls.x, Decl(a.js, 6, 30))
2224
}
2325
}

tests/baselines/reference/commonjsAccessExports.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ exports.x;
2424
this.x = 0;
2525
>this.x = 0 : 0
2626
>this.x : any
27-
>this : any
27+
>this : this
2828
>x : any
2929
>0 : 0
3030
}

tests/baselines/reference/constructorFunctionMergeWithClass.symbols

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ var SomeClass = function () {
33
>SomeClass : Symbol(SomeClass, Decl(file1.js, 0, 3), Decl(file2.js, 0, 0), Decl(file2.js, 0, 19))
44

55
this.otherProp = 0;
6+
>this.otherProp : Symbol(SomeClass.otherProp, Decl(file1.js, 0, 29))
7+
>this : Symbol(SomeClass, Decl(file1.js, 0, 15))
68
>otherProp : Symbol(SomeClass.otherProp, Decl(file1.js, 0, 29))
79

810
};

tests/baselines/reference/constructorFunctionMergeWithClass.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ var SomeClass = function () {
66
this.otherProp = 0;
77
>this.otherProp = 0 : 0
88
>this.otherProp : any
9-
>this : any
9+
>this : this
1010
>otherProp : any
1111
>0 : 0
1212

tests/baselines/reference/constructorFunctions.symbols

+6
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ function C1() {
33
>C1 : Symbol(C1, Decl(index.js, 0, 0))
44

55
if (!(this instanceof C1)) return new C1();
6+
>this : Symbol(C1, Decl(index.js, 0, 0))
67
>C1 : Symbol(C1, Decl(index.js, 0, 0))
78
>C1 : Symbol(C1, Decl(index.js, 0, 0))
89

910
this.x = 1;
11+
>this.x : Symbol(C1.x, Decl(index.js, 1, 47))
12+
>this : Symbol(C1, Decl(index.js, 0, 0))
1013
>x : Symbol(C1.x, Decl(index.js, 1, 47))
1114
}
1215

@@ -22,10 +25,13 @@ var C2 = function () {
2225
>C2 : Symbol(C2, Decl(index.js, 8, 3))
2326

2427
if (!(this instanceof C2)) return new C2();
28+
>this : Symbol(C2, Decl(index.js, 8, 8))
2529
>C2 : Symbol(C2, Decl(index.js, 8, 3))
2630
>C2 : Symbol(C2, Decl(index.js, 8, 3))
2731

2832
this.x = 1;
33+
>this.x : Symbol(C2.x, Decl(index.js, 9, 47))
34+
>this : Symbol(C2, Decl(index.js, 8, 8))
2935
>x : Symbol(C2.x, Decl(index.js, 9, 47))
3036

3137
};

tests/baselines/reference/constructorFunctions.types

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ function C1() {
66
>!(this instanceof C1) : boolean
77
>(this instanceof C1) : boolean
88
>this instanceof C1 : boolean
9-
>this : any
9+
>this : this
1010
>C1 : typeof C1
1111
>new C1() : C1
1212
>C1 : typeof C1
1313

1414
this.x = 1;
1515
>this.x = 1 : 1
1616
>this.x : any
17-
>this : any
17+
>this : this
1818
>x : any
1919
>1 : 1
2020
}
@@ -37,15 +37,15 @@ var C2 = function () {
3737
>!(this instanceof C2) : boolean
3838
>(this instanceof C2) : boolean
3939
>this instanceof C2 : boolean
40-
>this : any
40+
>this : this
4141
>C2 : typeof C2
4242
>new C2() : C2
4343
>C2 : typeof C2
4444

4545
this.x = 1;
4646
>this.x = 1 : 1
4747
>this.x : any
48-
>this : any
48+
>this : this
4949
>x : any
5050
>1 : 1
5151

tests/baselines/reference/constructorFunctions2.symbols

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ const a = new A().id;
2121

2222
const B = function() { this.id = 1; }
2323
>B : Symbol(B, Decl(index.js, 3, 5))
24+
>this.id : Symbol(B.id, Decl(index.js, 3, 22))
25+
>this : Symbol(B, Decl(index.js, 3, 9))
2426
>id : Symbol(B.id, Decl(index.js, 3, 22))
2527

2628
B.prototype.m = function() { this.x = 2; }
@@ -49,6 +51,8 @@ b.x;
4951
=== tests/cases/conformance/salsa/other.js ===
5052
function A() { this.id = 1; }
5153
>A : Symbol(A, Decl(other.js, 0, 0))
54+
>this.id : Symbol(A.id, Decl(other.js, 0, 14))
55+
>this : Symbol(A, Decl(other.js, 0, 0))
5256
>id : Symbol(A.id, Decl(other.js, 0, 14))
5357

5458
module.exports = A;

tests/baselines/reference/constructorFunctions2.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const B = function() { this.id = 1; }
2626
>function() { this.id = 1; } : typeof B
2727
>this.id = 1 : 1
2828
>this.id : any
29-
>this : any
29+
>this : this
3030
>id : any
3131
>1 : 1
3232

@@ -64,7 +64,7 @@ function A() { this.id = 1; }
6464
>A : typeof A
6565
>this.id = 1 : 1
6666
>this.id : any
67-
>this : any
67+
>this : this
6868
>id : any
6969
>1 : 1
7070

tests/baselines/reference/constructorFunctions3.symbols

+8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ function Instance() {
33
>Instance : Symbol(Instance, Decl(a.js, 0, 0))
44

55
this.i = 'simple'
6+
>this.i : Symbol(Instance.i, Decl(a.js, 0, 21))
7+
>this : Symbol(Instance, Decl(a.js, 0, 0))
68
>i : Symbol(Instance.i, Decl(a.js, 0, 21))
79
}
810
var i = new Instance();
@@ -19,6 +21,8 @@ function StaticToo() {
1921
>StaticToo : Symbol(StaticToo, Decl(a.js, 5, 2), Decl(a.js, 9, 1))
2022

2123
this.i = 'more complex'
24+
>this.i : Symbol(StaticToo.i, Decl(a.js, 7, 22))
25+
>this : Symbol(StaticToo, Decl(a.js, 5, 2), Decl(a.js, 9, 1))
2226
>i : Symbol(StaticToo.i, Decl(a.js, 7, 22))
2327
}
2428
StaticToo.property = 'yep'
@@ -41,10 +45,14 @@ function A () {
4145
>A : Symbol(A, Decl(a.js, 13, 10), Decl(a.js, 24, 1))
4246

4347
this.x = 1
48+
>this.x : Symbol(A.x, Decl(a.js, 16, 15))
49+
>this : Symbol(A, Decl(a.js, 13, 10), Decl(a.js, 24, 1))
4450
>x : Symbol(A.x, Decl(a.js, 16, 15))
4551

4652
/** @type {1} */
4753
this.second = 1
54+
>this.second : Symbol(A.second, Decl(a.js, 17, 14))
55+
>this : Symbol(A, Decl(a.js, 13, 10), Decl(a.js, 24, 1))
4856
>second : Symbol(A.second, Decl(a.js, 17, 14))
4957
}
5058
/** @param {number} n */

0 commit comments

Comments
 (0)