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

Lines changed: 12 additions & 25 deletions
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

Lines changed: 0 additions & 5 deletions
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

Lines changed: 4 additions & 1 deletion
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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 2 additions & 0 deletions
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
}

0 commit comments

Comments
 (0)