Skip to content

Commit 64ac5a5

Browse files
authored
Fixes for type parameter name resolution in JS (#26830)
* check for expando initializers in resolveEntityName when resolving type parameters in a prototype property assignment declaration. For example, this already works: ```js /** @template T */ function f(x) { this.x = x } /** @returns {T} */ f.protototype.m = function () { return this.x } ``` This now works too: ```js /** @template T */ var f = function (x) { this.x = x } /** @returns {T} */ f.prototype.m = function () { return this.x } ``` Fixes #26826 * Lookup type parameters on prototype-assignment methods In the same way that they're looked up on prototype-property methods. That is, this previously worked: ```js /** @template T */ function f() { } /** @param {T} p */ f.prototype.m = function () { } ``` And this now works too: ```js /** @template T */ function f() { } f.prototype = { /** @param {T} p */ m() { } } ``` Note that the baselines still have errors; I'll file a followup bug for them. * Look up types on property assignments too
1 parent 0ac3a0a commit 64ac5a5

File tree

8 files changed

+749
-2
lines changed

8 files changed

+749
-2
lines changed

src/compiler/checker.ts

+22-2
Original file line numberDiff line numberDiff line change
@@ -2140,7 +2140,7 @@ namespace ts {
21402140
}
21412141
}
21422142

2143-
function getJSSpecialAssignmentLocation(node: TypeReferenceNode): Declaration | undefined {
2143+
function getJSSpecialAssignmentLocation(node: TypeReferenceNode): Node | undefined {
21442144
const typeAlias = findAncestor(node, node => !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : isJSDocTypeAlias(node));
21452145
if (typeAlias) {
21462146
return;
@@ -2149,8 +2149,20 @@ namespace ts {
21492149
if (isExpressionStatement(host) &&
21502150
isBinaryExpression(host.expression) &&
21512151
getSpecialPropertyAssignmentKind(host.expression) === SpecialPropertyAssignmentKind.PrototypeProperty) {
2152+
// X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration
21522153
const symbol = getSymbolOfNode(host.expression.left);
2153-
return symbol && symbol.parent!.valueDeclaration;
2154+
if (symbol) {
2155+
return getDeclarationOfJSPrototypeContainer(symbol);
2156+
}
2157+
}
2158+
if ((isObjectLiteralMethod(host) || isPropertyAssignment(host)) &&
2159+
isBinaryExpression(host.parent.parent) &&
2160+
getSpecialPropertyAssignmentKind(host.parent.parent) === SpecialPropertyAssignmentKind.Prototype) {
2161+
// X.prototype = { /** @param {K} p */m() { } } <-- look for K on X's declaration
2162+
const symbol = getSymbolOfNode(host.parent.parent.left);
2163+
if (symbol) {
2164+
return getDeclarationOfJSPrototypeContainer(symbol);
2165+
}
21542166
}
21552167
const sig = getHostSignatureFromJSDocHost(host);
21562168
if (sig) {
@@ -2159,6 +2171,14 @@ namespace ts {
21592171
}
21602172
}
21612173

2174+
function getDeclarationOfJSPrototypeContainer(symbol: Symbol) {
2175+
const decl = symbol.parent!.valueDeclaration;
2176+
const initializer = isAssignmentDeclaration(decl) ? getAssignedJavascriptInitializer(decl) :
2177+
hasOnlyExpressionInitializer(decl) ? getDeclaredJavascriptInitializer(decl) :
2178+
undefined;
2179+
return initializer || decl;
2180+
}
2181+
21622182
function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression): Symbol | undefined {
21632183
return resolveExternalModuleNameWorker(location, moduleReferenceExpression, Diagnostics.Cannot_find_module_0);
21642184
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
=== tests/cases/conformance/jsdoc/a.js ===
2+
/**
3+
* Should work for function declarations
4+
* @constructor
5+
* @template {string} K
6+
* @template V
7+
*/
8+
function Multimap() {
9+
>Multimap : Symbol(Multimap, Decl(a.js, 0, 0))
10+
11+
/** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */
12+
this._map = {};
13+
>this._map : Symbol(Multimap._map, Decl(a.js, 6, 21))
14+
>this : Symbol(Multimap, Decl(a.js, 0, 0))
15+
>_map : Symbol(Multimap._map, Decl(a.js, 6, 21))
16+
17+
};
18+
19+
/**
20+
* @param {K} key the key ok
21+
* @returns {V} the value ok
22+
*/
23+
Multimap.prototype.get = function (key) {
24+
>Multimap.prototype : Symbol(Multimap.get, Decl(a.js, 9, 2))
25+
>Multimap : Symbol(Multimap, Decl(a.js, 0, 0))
26+
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
27+
>get : Symbol(Multimap.get, Decl(a.js, 9, 2))
28+
>key : Symbol(key, Decl(a.js, 15, 35))
29+
30+
return this._map[key + ''];
31+
>this._map : Symbol(Multimap._map, Decl(a.js, 6, 21))
32+
>this : Symbol(Multimap, Decl(a.js, 0, 0))
33+
>_map : Symbol(Multimap._map, Decl(a.js, 6, 21))
34+
>key : Symbol(key, Decl(a.js, 15, 35))
35+
}
36+
37+
/**
38+
* Should work for initialisers too
39+
* @constructor
40+
* @template {string} K
41+
* @template V
42+
*/
43+
var Multimap2 = function() {
44+
>Multimap2 : Symbol(Multimap2, Decl(a.js, 25, 3))
45+
46+
/** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */
47+
this._map = {};
48+
>this._map : Symbol(Multimap2._map, Decl(a.js, 25, 28))
49+
>this : Symbol(Multimap2, Decl(a.js, 25, 15))
50+
>_map : Symbol(Multimap2._map, Decl(a.js, 25, 28))
51+
52+
};
53+
54+
/**
55+
* @param {K} key the key ok
56+
* @returns {V} the value ok
57+
*/
58+
Multimap2.prototype.get = function (key) {
59+
>Multimap2.prototype : Symbol(Multimap2.get, Decl(a.js, 28, 2))
60+
>Multimap2 : Symbol(Multimap2, Decl(a.js, 25, 3))
61+
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
62+
>get : Symbol(Multimap2.get, Decl(a.js, 28, 2))
63+
>key : Symbol(key, Decl(a.js, 34, 36))
64+
65+
return this._map[key + ''];
66+
>this._map : Symbol(Multimap2._map, Decl(a.js, 25, 28))
67+
>this : Symbol(Multimap2, Decl(a.js, 25, 15))
68+
>_map : Symbol(Multimap2._map, Decl(a.js, 25, 28))
69+
>key : Symbol(key, Decl(a.js, 34, 36))
70+
}
71+
72+
var Ns = {};
73+
>Ns : Symbol(Ns, Decl(a.js, 38, 3), Decl(a.js, 38, 12))
74+
75+
/**
76+
* Should work for expando-namespaced initialisers too
77+
* @constructor
78+
* @template {string} K
79+
* @template V
80+
*/
81+
Ns.Multimap3 = function() {
82+
>Ns.Multimap3 : Symbol(Ns.Multimap3, Decl(a.js, 38, 12))
83+
>Ns : Symbol(Ns, Decl(a.js, 38, 3), Decl(a.js, 38, 12))
84+
>Multimap3 : Symbol(Ns.Multimap3, Decl(a.js, 38, 12))
85+
86+
/** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */
87+
this._map = {};
88+
>this._map : Symbol(Multimap3._map, Decl(a.js, 45, 27))
89+
>this : Symbol(Multimap3, Decl(a.js, 45, 14))
90+
>_map : Symbol(Multimap3._map, Decl(a.js, 45, 27))
91+
92+
};
93+
94+
/**
95+
* @param {K} key the key ok
96+
* @returns {V} the value ok
97+
*/
98+
Ns.Multimap3.prototype.get = function (key) {
99+
>Ns.Multimap3.prototype : Symbol(Ns.Multimap3.get, Decl(a.js, 48, 2))
100+
>Ns.Multimap3 : Symbol(Ns.Multimap3, Decl(a.js, 38, 12))
101+
>Ns : Symbol(Ns, Decl(a.js, 38, 3), Decl(a.js, 38, 12))
102+
>Multimap3 : Symbol(Ns.Multimap3, Decl(a.js, 38, 12))
103+
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
104+
>get : Symbol(Ns.Multimap3.get, Decl(a.js, 48, 2))
105+
>key : Symbol(key, Decl(a.js, 54, 39))
106+
107+
return this._map[key + ''];
108+
>this._map : Symbol(Multimap3._map, Decl(a.js, 45, 27))
109+
>this : Symbol(Multimap3, Decl(a.js, 45, 14))
110+
>_map : Symbol(Multimap3._map, Decl(a.js, 45, 27))
111+
>key : Symbol(key, Decl(a.js, 54, 39))
112+
}
113+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
=== tests/cases/conformance/jsdoc/a.js ===
2+
/**
3+
* Should work for function declarations
4+
* @constructor
5+
* @template {string} K
6+
* @template V
7+
*/
8+
function Multimap() {
9+
>Multimap : typeof Multimap
10+
11+
/** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */
12+
this._map = {};
13+
>this._map = {} : {}
14+
>this._map : { [x: string]: V; }
15+
>this : Multimap
16+
>_map : { [x: string]: V; }
17+
>{} : {}
18+
19+
};
20+
21+
/**
22+
* @param {K} key the key ok
23+
* @returns {V} the value ok
24+
*/
25+
Multimap.prototype.get = function (key) {
26+
>Multimap.prototype.get = function (key) { return this._map[key + ''];} : (key: K) => V
27+
>Multimap.prototype.get : any
28+
>Multimap.prototype : any
29+
>Multimap : typeof Multimap
30+
>prototype : any
31+
>get : any
32+
>function (key) { return this._map[key + ''];} : (key: K) => V
33+
>key : K
34+
35+
return this._map[key + ''];
36+
>this._map[key + ''] : V
37+
>this._map : { [x: string]: V; }
38+
>this : Multimap
39+
>_map : { [x: string]: V; }
40+
>key + '' : string
41+
>key : K
42+
>'' : ""
43+
}
44+
45+
/**
46+
* Should work for initialisers too
47+
* @constructor
48+
* @template {string} K
49+
* @template V
50+
*/
51+
var Multimap2 = function() {
52+
>Multimap2 : typeof Multimap2
53+
>function() { /** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */ this._map = {};} : typeof Multimap2
54+
55+
/** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */
56+
this._map = {};
57+
>this._map = {} : {}
58+
>this._map : { [x: string]: V; }
59+
>this : Multimap2
60+
>_map : { [x: string]: V; }
61+
>{} : {}
62+
63+
};
64+
65+
/**
66+
* @param {K} key the key ok
67+
* @returns {V} the value ok
68+
*/
69+
Multimap2.prototype.get = function (key) {
70+
>Multimap2.prototype.get = function (key) { return this._map[key + ''];} : (key: K) => V
71+
>Multimap2.prototype.get : any
72+
>Multimap2.prototype : any
73+
>Multimap2 : typeof Multimap2
74+
>prototype : any
75+
>get : any
76+
>function (key) { return this._map[key + ''];} : (key: K) => V
77+
>key : K
78+
79+
return this._map[key + ''];
80+
>this._map[key + ''] : V
81+
>this._map : { [x: string]: V; }
82+
>this : Multimap2
83+
>_map : { [x: string]: V; }
84+
>key + '' : string
85+
>key : K
86+
>'' : ""
87+
}
88+
89+
var Ns = {};
90+
>Ns : typeof Ns
91+
>{} : {}
92+
93+
/**
94+
* Should work for expando-namespaced initialisers too
95+
* @constructor
96+
* @template {string} K
97+
* @template V
98+
*/
99+
Ns.Multimap3 = function() {
100+
>Ns.Multimap3 = function() { /** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */ this._map = {};} : typeof Multimap3
101+
>Ns.Multimap3 : typeof Multimap3
102+
>Ns : typeof Ns
103+
>Multimap3 : typeof Multimap3
104+
>function() { /** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */ this._map = {};} : typeof Multimap3
105+
106+
/** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */
107+
this._map = {};
108+
>this._map = {} : {}
109+
>this._map : { [x: string]: V; }
110+
>this : Multimap3
111+
>_map : { [x: string]: V; }
112+
>{} : {}
113+
114+
};
115+
116+
/**
117+
* @param {K} key the key ok
118+
* @returns {V} the value ok
119+
*/
120+
Ns.Multimap3.prototype.get = function (key) {
121+
>Ns.Multimap3.prototype.get = function (key) { return this._map[key + ''];} : (key: K) => V
122+
>Ns.Multimap3.prototype.get : any
123+
>Ns.Multimap3.prototype : any
124+
>Ns.Multimap3 : typeof Multimap3
125+
>Ns : typeof Ns
126+
>Multimap3 : typeof Multimap3
127+
>prototype : any
128+
>get : any
129+
>function (key) { return this._map[key + ''];} : (key: K) => V
130+
>key : K
131+
132+
return this._map[key + ''];
133+
>this._map[key + ''] : V
134+
>this._map : { [x: string]: V; }
135+
>this : Multimap3
136+
>_map : { [x: string]: V; }
137+
>key + '' : string
138+
>key : K
139+
>'' : ""
140+
}
141+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
tests/cases/conformance/jsdoc/a.js(18,21): error TS2339: Property '_map' does not exist on type '{ get(key: K): V; }'.
2+
tests/cases/conformance/jsdoc/a.js(39,21): error TS2339: Property '_map' does not exist on type '{ get: (key: K) => V; }'.
3+
tests/cases/conformance/jsdoc/a.js(61,21): error TS2339: Property '_map' does not exist on type '{ get(key: K): V; }'.
4+
5+
6+
==== tests/cases/conformance/jsdoc/a.js (3 errors) ====
7+
/**
8+
* Should work for function declarations
9+
* @constructor
10+
* @template {string} K
11+
* @template V
12+
*/
13+
function Multimap() {
14+
/** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */
15+
this._map = {};
16+
};
17+
18+
Multimap.prototype = {
19+
/**
20+
* @param {K} key the key ok
21+
* @returns {V} the value ok
22+
*/
23+
get(key) {
24+
return this._map[key + ''];
25+
~~~~
26+
!!! error TS2339: Property '_map' does not exist on type '{ get(key: K): V; }'.
27+
}
28+
}
29+
30+
/**
31+
* Should work for initialisers too
32+
* @constructor
33+
* @template {string} K
34+
* @template V
35+
*/
36+
var Multimap2 = function() {
37+
/** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */
38+
this._map = {};
39+
};
40+
41+
Multimap2.prototype = {
42+
/**
43+
* @param {K} key the key ok
44+
* @returns {V} the value ok
45+
*/
46+
get: function(key) {
47+
return this._map[key + ''];
48+
~~~~
49+
!!! error TS2339: Property '_map' does not exist on type '{ get: (key: K) => V; }'.
50+
}
51+
}
52+
53+
var Ns = {};
54+
/**
55+
* Should work for expando-namespaced initialisers too
56+
* @constructor
57+
* @template {string} K
58+
* @template V
59+
*/
60+
Ns.Multimap3 = function() {
61+
/** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */
62+
this._map = {};
63+
};
64+
65+
Ns.Multimap3.prototype = {
66+
/**
67+
* @param {K} key the key ok
68+
* @returns {V} the value ok
69+
*/
70+
get(key) {
71+
return this._map[key + ''];
72+
~~~~
73+
!!! error TS2339: Property '_map' does not exist on type '{ get(key: K): V; }'.
74+
}
75+
}
76+
77+

0 commit comments

Comments
 (0)