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
This commit is contained in:
Nathan Shively-Sanders 2018-09-12 16:21:17 -07:00 committed by GitHub
parent 2f8a646f8e
commit 614423b287
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 416 additions and 105 deletions

View file

@ -15809,30 +15809,27 @@ namespace ts {
}
function tryGetThisTypeAt(node: Node, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined {
const isInJS = isInJSFile(node);
if (isFunctionLike(container) &&
(!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) {
// Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated.
// If this is a function in a JS file, it might be a class method.
// Check if it's the RHS of a x.prototype.y = function [name]() { .... }
if (container.kind === SyntaxKind.FunctionExpression &&
container.parent.kind === SyntaxKind.BinaryExpression &&
getAssignmentDeclarationKind(container.parent as BinaryExpression) === AssignmentDeclarationKind.PrototypeProperty) {
// Get the 'x' of 'x.prototype.y = f' (here, 'f' is 'container')
const className = (((container.parent as BinaryExpression) // x.prototype.y = f
.left as PropertyAccessExpression) // x.prototype.y
.expression as PropertyAccessExpression) // x.prototype
.expression; // x
const className = getClassNameFromPrototypeMethod(container);
if (isInJS && className) {
const classSymbol = checkExpression(className).symbol;
if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) {
return getFlowTypeOfReference(node, getInferredClassType(classSymbol));
const classType = getJavascriptClassType(classSymbol);
if (classType) {
return getFlowTypeOfReference(node, classType);
}
}
}
// Check if it's a constructor definition, can be either a variable decl or function decl
// i.e.
// * /** @constructor */ function [name]() { ... }
// * /** @constructor */ var x = function() { ... }
else if ((container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) &&
else if (isInJS &&
(container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) &&
getJSDocClassTag(container)) {
const classType = getJavascriptClassType(container.symbol);
if (classType) {
@ -15852,7 +15849,7 @@ namespace ts {
return getFlowTypeOfReference(node, type);
}
if (isInJSFile(node)) {
if (isInJS) {
const type = getTypeForThisExpressionFromJSDoc(container);
if (type && type !== errorType) {
return getFlowTypeOfReference(node, type);
@ -15860,6 +15857,34 @@ namespace ts {
}
}
function getClassNameFromPrototypeMethod(container: Node) {
// Check if it's the RHS of a x.prototype.y = function [name]() { .... }
if (container.kind === SyntaxKind.FunctionExpression &&
isBinaryExpression(container.parent) &&
getAssignmentDeclarationKind(container.parent) === AssignmentDeclarationKind.PrototypeProperty) {
// Get the 'x' of 'x.prototype.y = container'
return ((container.parent // x.prototype.y = container
.left as PropertyAccessExpression) // x.prototype.y
.expression as PropertyAccessExpression) // x.prototype
.expression; // x
}
// x.prototype = { method() { } }
else if (container.kind === SyntaxKind.MethodDeclaration &&
container.parent.kind === SyntaxKind.ObjectLiteralExpression &&
isBinaryExpression(container.parent.parent) &&
getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.Prototype) {
return (container.parent.parent.left as PropertyAccessExpression).expression;
}
// x.prototype = { method: function() { } }
else if (container.kind === SyntaxKind.FunctionExpression &&
container.parent.kind === SyntaxKind.PropertyAssignment &&
container.parent.parent.kind === SyntaxKind.ObjectLiteralExpression &&
isBinaryExpression(container.parent.parent.parent) &&
getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.Prototype) {
return (container.parent.parent.parent.left as PropertyAccessExpression).expression;
}
}
function getTypeForThisExpressionFromJSDoc(node: Node) {
const jsdocType = getJSDocType(node);
if (jsdocType && jsdocType.kind === SyntaxKind.JSDocFunctionType) {

View file

@ -1,77 +0,0 @@
tests/cases/conformance/jsdoc/a.js(18,21): error TS2339: Property '_map' does not exist on type '{ get(key: K): V; }'.
tests/cases/conformance/jsdoc/a.js(39,21): error TS2339: Property '_map' does not exist on type '{ get: (key: K) => V; }'.
tests/cases/conformance/jsdoc/a.js(61,21): error TS2339: Property '_map' does not exist on type '{ get(key: K): V; }'.
==== tests/cases/conformance/jsdoc/a.js (3 errors) ====
/**
* Should work for function declarations
* @constructor
* @template {string} K
* @template V
*/
function Multimap() {
/** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */
this._map = {};
};
Multimap.prototype = {
/**
* @param {K} key the key ok
* @returns {V} the value ok
*/
get(key) {
return this._map[key + ''];
~~~~
!!! error TS2339: Property '_map' does not exist on type '{ get(key: K): V; }'.
}
}
/**
* Should work for initialisers too
* @constructor
* @template {string} K
* @template V
*/
var Multimap2 = function() {
/** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */
this._map = {};
};
Multimap2.prototype = {
/**
* @param {K} key the key ok
* @returns {V} the value ok
*/
get: function(key) {
return this._map[key + ''];
~~~~
!!! error TS2339: Property '_map' does not exist on type '{ get: (key: K) => V; }'.
}
}
var Ns = {};
/**
* Should work for expando-namespaced initialisers too
* @constructor
* @template {string} K
* @template V
*/
Ns.Multimap3 = function() {
/** @type {Object<string, V>} TODO: Remove the prototype from the fresh object */
this._map = {};
};
Ns.Multimap3.prototype = {
/**
* @param {K} key the key ok
* @returns {V} the value ok
*/
get(key) {
return this._map[key + ''];
~~~~
!!! error TS2339: Property '_map' does not exist on type '{ get(key: K): V; }'.
}
}

View file

@ -29,7 +29,8 @@ Multimap.prototype = {
>key : Symbol(key, Decl(a.js, 16, 8))
return this._map[key + ''];
>this : Symbol(__object, Decl(a.js, 11, 20))
>this._map : Symbol(Multimap._map, Decl(a.js, 6, 21))
>_map : Symbol(Multimap._map, Decl(a.js, 6, 21))
>key : Symbol(key, Decl(a.js, 16, 8))
}
}
@ -64,7 +65,8 @@ Multimap2.prototype = {
>key : Symbol(key, Decl(a.js, 37, 18))
return this._map[key + ''];
>this : Symbol(__object, Decl(a.js, 32, 21))
>this._map : Symbol(Multimap2._map, Decl(a.js, 27, 28))
>_map : Symbol(Multimap2._map, Decl(a.js, 27, 28))
>key : Symbol(key, Decl(a.js, 37, 18))
}
}
@ -106,7 +108,8 @@ Ns.Multimap3.prototype = {
>key : Symbol(key, Decl(a.js, 59, 8))
return this._map[key + ''];
>this : Symbol(__object, Decl(a.js, 54, 24))
>this._map : Symbol(Multimap3._map, Decl(a.js, 49, 27))
>_map : Symbol(Multimap3._map, Decl(a.js, 49, 27))
>key : Symbol(key, Decl(a.js, 59, 8))
}
}

View file

@ -34,10 +34,10 @@ Multimap.prototype = {
>key : K
return this._map[key + ''];
>this._map[key + ''] : any
>this._map : any
>this : { get(key: K): V; }
>_map : any
>this._map[key + ''] : V
>this._map : { [x: string]: V; }
>this : Multimap & { get(key: K): V; }
>_map : { [x: string]: V; }
>key + '' : string
>key : K
>'' : ""
@ -81,10 +81,10 @@ Multimap2.prototype = {
>key : K
return this._map[key + ''];
>this._map[key + ''] : any
>this._map : any
>this : { get: (key: K) => V; }
>_map : any
>this._map[key + ''] : V
>this._map : { [x: string]: V; }
>this : Multimap2 & { get: (key: K) => V; }
>_map : { [x: string]: V; }
>key + '' : string
>key : K
>'' : ""
@ -136,10 +136,10 @@ Ns.Multimap3.prototype = {
>key : K
return this._map[key + ''];
>this._map[key + ''] : any
>this._map : any
>this : { get(key: K): V; }
>_map : any
>this._map[key + ''] : V
>this._map : { [x: string]: V; }
>this : Multimap3 & { get(key: K): V; }
>_map : { [x: string]: V; }
>key + '' : string
>key : K
>'' : ""

View file

@ -0,0 +1,45 @@
tests/cases/conformance/salsa/a.js(27,20): error TS2339: Property 'addon' does not exist on type '{ set: () => void; get(): void; }'.
==== tests/cases/conformance/salsa/a.js (1 errors) ====
// all references to _map, set, get, addon should be ok
/** @constructor */
var Multimap = function() {
this._map = {};
this._map
this.set
this.get
this.addon
};
Multimap.prototype = {
set: function() {
this._map
this.set
this.get
this.addon
},
get() {
this._map
this.set
this.get
this.addon
}
}
Multimap.prototype.addon = function () {
~~~~~
!!! error TS2339: Property 'addon' does not exist on type '{ set: () => void; get(): void; }'.
this._map
this.set
this.get
this.addon
}
var mm = new Multimap();
mm._map
mm.set
mm.get
mm.addon

View file

@ -0,0 +1,122 @@
=== tests/cases/conformance/salsa/a.js ===
// all references to _map, set, get, addon should be ok
/** @constructor */
var Multimap = function() {
>Multimap : Symbol(Multimap, Decl(a.js, 3, 3), Decl(a.js, 9, 2))
this._map = {};
>this._map : Symbol(Multimap._map, Decl(a.js, 3, 27))
>_map : Symbol(Multimap._map, Decl(a.js, 3, 27))
this._map
>this._map : Symbol(Multimap._map, Decl(a.js, 3, 27))
>_map : Symbol(Multimap._map, Decl(a.js, 3, 27))
this.set
>this.set : Symbol(set, Decl(a.js, 11, 22))
>set : Symbol(set, Decl(a.js, 11, 22))
this.get
>this.get : Symbol(get, Decl(a.js, 17, 6))
>get : Symbol(get, Decl(a.js, 17, 6))
this.addon
>this.addon : Symbol(Multimap.addon, Decl(a.js, 24, 1))
>addon : Symbol(Multimap.addon, Decl(a.js, 24, 1))
};
Multimap.prototype = {
>Multimap.prototype : Symbol(Multimap.prototype, Decl(a.js, 9, 2))
>Multimap : Symbol(Multimap, Decl(a.js, 3, 3), Decl(a.js, 9, 2))
>prototype : Symbol(Multimap.prototype, Decl(a.js, 9, 2))
set: function() {
>set : Symbol(set, Decl(a.js, 11, 22))
this._map
>this._map : Symbol(Multimap._map, Decl(a.js, 3, 27))
>_map : Symbol(Multimap._map, Decl(a.js, 3, 27))
this.set
>this.set : Symbol(set, Decl(a.js, 11, 22))
>set : Symbol(set, Decl(a.js, 11, 22))
this.get
>this.get : Symbol(get, Decl(a.js, 17, 6))
>get : Symbol(get, Decl(a.js, 17, 6))
this.addon
>this.addon : Symbol(Multimap.addon, Decl(a.js, 24, 1))
>addon : Symbol(Multimap.addon, Decl(a.js, 24, 1))
},
get() {
>get : Symbol(get, Decl(a.js, 17, 6))
this._map
>this._map : Symbol(Multimap._map, Decl(a.js, 3, 27))
>_map : Symbol(Multimap._map, Decl(a.js, 3, 27))
this.set
>this.set : Symbol(set, Decl(a.js, 11, 22))
>set : Symbol(set, Decl(a.js, 11, 22))
this.get
>this.get : Symbol(get, Decl(a.js, 17, 6))
>get : Symbol(get, Decl(a.js, 17, 6))
this.addon
>this.addon : Symbol(Multimap.addon, Decl(a.js, 24, 1))
>addon : Symbol(Multimap.addon, Decl(a.js, 24, 1))
}
}
Multimap.prototype.addon = function () {
>Multimap.prototype : Symbol(Multimap.addon, Decl(a.js, 24, 1))
>Multimap : Symbol(Multimap, Decl(a.js, 3, 3), Decl(a.js, 9, 2))
>prototype : Symbol(Multimap.prototype, Decl(a.js, 9, 2))
>addon : Symbol(Multimap.addon, Decl(a.js, 24, 1))
this._map
>this._map : Symbol(Multimap._map, Decl(a.js, 3, 27))
>_map : Symbol(Multimap._map, Decl(a.js, 3, 27))
this.set
>this.set : Symbol(set, Decl(a.js, 11, 22))
>set : Symbol(set, Decl(a.js, 11, 22))
this.get
>this.get : Symbol(get, Decl(a.js, 17, 6))
>get : Symbol(get, Decl(a.js, 17, 6))
this.addon
>this.addon : Symbol(Multimap.addon, Decl(a.js, 24, 1))
>addon : Symbol(Multimap.addon, Decl(a.js, 24, 1))
}
var mm = new Multimap();
>mm : Symbol(mm, Decl(a.js, 33, 3))
>Multimap : Symbol(Multimap, Decl(a.js, 3, 3), Decl(a.js, 9, 2))
mm._map
>mm._map : Symbol(Multimap._map, Decl(a.js, 3, 27))
>mm : Symbol(mm, Decl(a.js, 33, 3))
>_map : Symbol(Multimap._map, Decl(a.js, 3, 27))
mm.set
>mm.set : Symbol(set, Decl(a.js, 11, 22))
>mm : Symbol(mm, Decl(a.js, 33, 3))
>set : Symbol(set, Decl(a.js, 11, 22))
mm.get
>mm.get : Symbol(get, Decl(a.js, 17, 6))
>mm : Symbol(mm, Decl(a.js, 33, 3))
>get : Symbol(get, Decl(a.js, 17, 6))
mm.addon
>mm.addon : Symbol(Multimap.addon, Decl(a.js, 24, 1))
>mm : Symbol(mm, Decl(a.js, 33, 3))
>addon : Symbol(Multimap.addon, Decl(a.js, 24, 1))

View file

@ -0,0 +1,149 @@
=== tests/cases/conformance/salsa/a.js ===
// all references to _map, set, get, addon should be ok
/** @constructor */
var Multimap = function() {
>Multimap : typeof Multimap
>function() { this._map = {}; this._map this.set this.get this.addon} : typeof Multimap
this._map = {};
>this._map = {} : {}
>this._map : {}
>this : Multimap & { set: () => void; get(): void; }
>_map : {}
>{} : {}
this._map
>this._map : {}
>this : Multimap & { set: () => void; get(): void; }
>_map : {}
this.set
>this.set : () => void
>this : Multimap & { set: () => void; get(): void; }
>set : () => void
this.get
>this.get : () => void
>this : Multimap & { set: () => void; get(): void; }
>get : () => void
this.addon
>this.addon : () => void
>this : Multimap & { set: () => void; get(): void; }
>addon : () => void
};
Multimap.prototype = {
>Multimap.prototype = { set: function() { this._map this.set this.get this.addon }, get() { this._map this.set this.get this.addon }} : { set: () => void; get(): void; }
>Multimap.prototype : { set: () => void; get(): void; }
>Multimap : typeof Multimap
>prototype : { set: () => void; get(): void; }
>{ set: function() { this._map this.set this.get this.addon }, get() { this._map this.set this.get this.addon }} : { set: () => void; get(): void; }
set: function() {
>set : () => void
>function() { this._map this.set this.get this.addon } : () => void
this._map
>this._map : {}
>this : Multimap & { set: () => void; get(): void; }
>_map : {}
this.set
>this.set : () => void
>this : Multimap & { set: () => void; get(): void; }
>set : () => void
this.get
>this.get : () => void
>this : Multimap & { set: () => void; get(): void; }
>get : () => void
this.addon
>this.addon : () => void
>this : Multimap & { set: () => void; get(): void; }
>addon : () => void
},
get() {
>get : () => void
this._map
>this._map : {}
>this : Multimap & { set: () => void; get(): void; }
>_map : {}
this.set
>this.set : () => void
>this : Multimap & { set: () => void; get(): void; }
>set : () => void
this.get
>this.get : () => void
>this : Multimap & { set: () => void; get(): void; }
>get : () => void
this.addon
>this.addon : () => void
>this : Multimap & { set: () => void; get(): void; }
>addon : () => void
}
}
Multimap.prototype.addon = function () {
>Multimap.prototype.addon = function () { this._map this.set this.get this.addon} : () => void
>Multimap.prototype.addon : any
>Multimap.prototype : { set: () => void; get(): void; }
>Multimap : typeof Multimap
>prototype : { set: () => void; get(): void; }
>addon : any
>function () { this._map this.set this.get this.addon} : () => void
this._map
>this._map : {}
>this : Multimap & { set: () => void; get(): void; }
>_map : {}
this.set
>this.set : () => void
>this : Multimap & { set: () => void; get(): void; }
>set : () => void
this.get
>this.get : () => void
>this : Multimap & { set: () => void; get(): void; }
>get : () => void
this.addon
>this.addon : () => void
>this : Multimap & { set: () => void; get(): void; }
>addon : () => void
}
var mm = new Multimap();
>mm : Multimap & { set: () => void; get(): void; }
>new Multimap() : Multimap & { set: () => void; get(): void; }
>Multimap : typeof Multimap
mm._map
>mm._map : {}
>mm : Multimap & { set: () => void; get(): void; }
>_map : {}
mm.set
>mm.set : () => void
>mm : Multimap & { set: () => void; get(): void; }
>set : () => void
mm.get
>mm.get : () => void
>mm : Multimap & { set: () => void; get(): void; }
>get : () => void
mm.addon
>mm.addon : () => void
>mm : Multimap & { set: () => void; get(): void; }
>addon : () => void

View file

@ -0,0 +1,44 @@
// @noEmit: true
// @allowJs: true
// @checkJs: true
// @Filename: a.js
// @strict: true
// all references to _map, set, get, addon should be ok
/** @constructor */
var Multimap = function() {
this._map = {};
this._map
this.set
this.get
this.addon
};
Multimap.prototype = {
set: function() {
this._map
this.set
this.get
this.addon
},
get() {
this._map
this.set
this.get
this.addon
}
}
Multimap.prototype.addon = function () {
this._map
this.set
this.get
this.addon
}
var mm = new Multimap();
mm._map
mm.set
mm.get
mm.addon