Skip to content

Commit 614423b

Browse files
authored
Fix this-type in prototype-assigned object literals (#26925)
* Fix this-type in prototype-assigned object literals Some cases were missing from tryGetThisTypeAt. Fixes #26831 * Lookup this in JS only for @constructor+prototype assignments
1 parent 2f8a646 commit 614423b

8 files changed

+416
-105
lines changed

src/compiler/checker.ts

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15809,30 +15809,27 @@ namespace ts {
1580915809
}
1581015810

1581115811
function tryGetThisTypeAt(node: Node, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined {
15812+
const isInJS = isInJSFile(node);
1581215813
if (isFunctionLike(container) &&
1581315814
(!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) {
1581415815
// Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated.
15815-
1581615816
// If this is a function in a JS file, it might be a class method.
15817-
// Check if it's the RHS of a x.prototype.y = function [name]() { .... }
15818-
if (container.kind === SyntaxKind.FunctionExpression &&
15819-
container.parent.kind === SyntaxKind.BinaryExpression &&
15820-
getAssignmentDeclarationKind(container.parent as BinaryExpression) === AssignmentDeclarationKind.PrototypeProperty) {
15821-
// Get the 'x' of 'x.prototype.y = f' (here, 'f' is 'container')
15822-
const className = (((container.parent as BinaryExpression) // x.prototype.y = f
15823-
.left as PropertyAccessExpression) // x.prototype.y
15824-
.expression as PropertyAccessExpression) // x.prototype
15825-
.expression; // x
15817+
const className = getClassNameFromPrototypeMethod(container);
15818+
if (isInJS && className) {
1582615819
const classSymbol = checkExpression(className).symbol;
1582715820
if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) {
15828-
return getFlowTypeOfReference(node, getInferredClassType(classSymbol));
15821+
const classType = getJavascriptClassType(classSymbol);
15822+
if (classType) {
15823+
return getFlowTypeOfReference(node, classType);
15824+
}
1582915825
}
1583015826
}
1583115827
// Check if it's a constructor definition, can be either a variable decl or function decl
1583215828
// i.e.
1583315829
// * /** @constructor */ function [name]() { ... }
1583415830
// * /** @constructor */ var x = function() { ... }
15835-
else if ((container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) &&
15831+
else if (isInJS &&
15832+
(container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) &&
1583615833
getJSDocClassTag(container)) {
1583715834
const classType = getJavascriptClassType(container.symbol);
1583815835
if (classType) {
@@ -15852,14 +15849,42 @@ namespace ts {
1585215849
return getFlowTypeOfReference(node, type);
1585315850
}
1585415851

15855-
if (isInJSFile(node)) {
15852+
if (isInJS) {
1585615853
const type = getTypeForThisExpressionFromJSDoc(container);
1585715854
if (type && type !== errorType) {
1585815855
return getFlowTypeOfReference(node, type);
1585915856
}
1586015857
}
1586115858
}
1586215859

15860+
function getClassNameFromPrototypeMethod(container: Node) {
15861+
// Check if it's the RHS of a x.prototype.y = function [name]() { .... }
15862+
if (container.kind === SyntaxKind.FunctionExpression &&
15863+
isBinaryExpression(container.parent) &&
15864+
getAssignmentDeclarationKind(container.parent) === AssignmentDeclarationKind.PrototypeProperty) {
15865+
// Get the 'x' of 'x.prototype.y = container'
15866+
return ((container.parent // x.prototype.y = container
15867+
.left as PropertyAccessExpression) // x.prototype.y
15868+
.expression as PropertyAccessExpression) // x.prototype
15869+
.expression; // x
15870+
}
15871+
// x.prototype = { method() { } }
15872+
else if (container.kind === SyntaxKind.MethodDeclaration &&
15873+
container.parent.kind === SyntaxKind.ObjectLiteralExpression &&
15874+
isBinaryExpression(container.parent.parent) &&
15875+
getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.Prototype) {
15876+
return (container.parent.parent.left as PropertyAccessExpression).expression;
15877+
}
15878+
// x.prototype = { method: function() { } }
15879+
else if (container.kind === SyntaxKind.FunctionExpression &&
15880+
container.parent.kind === SyntaxKind.PropertyAssignment &&
15881+
container.parent.parent.kind === SyntaxKind.ObjectLiteralExpression &&
15882+
isBinaryExpression(container.parent.parent.parent) &&
15883+
getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.Prototype) {
15884+
return (container.parent.parent.parent.left as PropertyAccessExpression).expression;
15885+
}
15886+
}
15887+
1586315888
function getTypeForThisExpressionFromJSDoc(node: Node) {
1586415889
const jsdocType = getJSDocType(node);
1586515890
if (jsdocType && jsdocType.kind === SyntaxKind.JSDocFunctionType) {

tests/baselines/reference/jsdocTemplateTag5.errors.txt

Lines changed: 0 additions & 77 deletions
This file was deleted.

tests/baselines/reference/jsdocTemplateTag5.symbols

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ Multimap.prototype = {
2929
>key : Symbol(key, Decl(a.js, 16, 8))
3030

3131
return this._map[key + ''];
32-
>this : Symbol(__object, Decl(a.js, 11, 20))
32+
>this._map : Symbol(Multimap._map, Decl(a.js, 6, 21))
33+
>_map : Symbol(Multimap._map, Decl(a.js, 6, 21))
3334
>key : Symbol(key, Decl(a.js, 16, 8))
3435
}
3536
}
@@ -64,7 +65,8 @@ Multimap2.prototype = {
6465
>key : Symbol(key, Decl(a.js, 37, 18))
6566

6667
return this._map[key + ''];
67-
>this : Symbol(__object, Decl(a.js, 32, 21))
68+
>this._map : Symbol(Multimap2._map, Decl(a.js, 27, 28))
69+
>_map : Symbol(Multimap2._map, Decl(a.js, 27, 28))
6870
>key : Symbol(key, Decl(a.js, 37, 18))
6971
}
7072
}
@@ -106,7 +108,8 @@ Ns.Multimap3.prototype = {
106108
>key : Symbol(key, Decl(a.js, 59, 8))
107109

108110
return this._map[key + ''];
109-
>this : Symbol(__object, Decl(a.js, 54, 24))
111+
>this._map : Symbol(Multimap3._map, Decl(a.js, 49, 27))
112+
>_map : Symbol(Multimap3._map, Decl(a.js, 49, 27))
110113
>key : Symbol(key, Decl(a.js, 59, 8))
111114
}
112115
}

tests/baselines/reference/jsdocTemplateTag5.types

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ Multimap.prototype = {
3434
>key : K
3535

3636
return this._map[key + ''];
37-
>this._map[key + ''] : any
38-
>this._map : any
39-
>this : { get(key: K): V; }
40-
>_map : any
37+
>this._map[key + ''] : V
38+
>this._map : { [x: string]: V; }
39+
>this : Multimap & { get(key: K): V; }
40+
>_map : { [x: string]: V; }
4141
>key + '' : string
4242
>key : K
4343
>'' : ""
@@ -81,10 +81,10 @@ Multimap2.prototype = {
8181
>key : K
8282

8383
return this._map[key + ''];
84-
>this._map[key + ''] : any
85-
>this._map : any
86-
>this : { get: (key: K) => V; }
87-
>_map : any
84+
>this._map[key + ''] : V
85+
>this._map : { [x: string]: V; }
86+
>this : Multimap2 & { get: (key: K) => V; }
87+
>_map : { [x: string]: V; }
8888
>key + '' : string
8989
>key : K
9090
>'' : ""
@@ -136,10 +136,10 @@ Ns.Multimap3.prototype = {
136136
>key : K
137137

138138
return this._map[key + ''];
139-
>this._map[key + ''] : any
140-
>this._map : any
141-
>this : { get(key: K): V; }
142-
>_map : any
139+
>this._map[key + ''] : V
140+
>this._map : { [x: string]: V; }
141+
>this : Multimap3 & { get(key: K): V; }
142+
>_map : { [x: string]: V; }
143143
>key + '' : string
144144
>key : K
145145
>'' : ""
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
tests/cases/conformance/salsa/a.js(27,20): error TS2339: Property 'addon' does not exist on type '{ set: () => void; get(): void; }'.
2+
3+
4+
==== tests/cases/conformance/salsa/a.js (1 errors) ====
5+
// all references to _map, set, get, addon should be ok
6+
7+
/** @constructor */
8+
var Multimap = function() {
9+
this._map = {};
10+
this._map
11+
this.set
12+
this.get
13+
this.addon
14+
};
15+
16+
Multimap.prototype = {
17+
set: function() {
18+
this._map
19+
this.set
20+
this.get
21+
this.addon
22+
},
23+
get() {
24+
this._map
25+
this.set
26+
this.get
27+
this.addon
28+
}
29+
}
30+
31+
Multimap.prototype.addon = function () {
32+
~~~~~
33+
!!! error TS2339: Property 'addon' does not exist on type '{ set: () => void; get(): void; }'.
34+
this._map
35+
this.set
36+
this.get
37+
this.addon
38+
}
39+
40+
var mm = new Multimap();
41+
mm._map
42+
mm.set
43+
mm.get
44+
mm.addon
45+

0 commit comments

Comments
 (0)