Skip to content

Fix this-type in prototype-assigned object literals #26925

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 38 additions & 13 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -15852,14 +15849,42 @@ namespace ts {
return getFlowTypeOfReference(node, type);
}

if (isInJSFile(node)) {
if (isInJS) {
const type = getTypeForThisExpressionFromJSDoc(container);
if (type && type !== errorType) {
return getFlowTypeOfReference(node, type);
}
}
}

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) {
Expand Down
77 changes: 0 additions & 77 deletions tests/baselines/reference/jsdocTemplateTag5.errors.txt

This file was deleted.

9 changes: 6 additions & 3 deletions tests/baselines/reference/jsdocTemplateTag5.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
Expand Down Expand Up @@ -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))
}
}
Expand Down Expand Up @@ -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))
}
}
Expand Down
24 changes: 12 additions & 12 deletions tests/baselines/reference/jsdocTemplateTag5.types
Original file line number Diff line number Diff line change
Expand Up @@ -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
>'' : ""
Expand Down Expand Up @@ -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
>'' : ""
Expand Down Expand Up @@ -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
>'' : ""
Expand Down
45 changes: 45 additions & 0 deletions tests/baselines/reference/typeFromPrototypeAssignment.errors.txt
Original file line number Diff line number Diff line change
@@ -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

Loading