From 69f9f9b83d4fbe808766566c97b762ff4964f347 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 14 Sep 2018 10:11:25 -0700 Subject: [PATCH 1/2] Fix non-toplevel prototype assignment binder was using the wrong node to lookup the containing class type for prototype assignment, so it incorrectly put the prototype declaration on the class' symbol. This correction to the binder in turn required a change in getJSClassType in the checker. It now has to look at the "prototype" property for the prototype instead of looking on the class symbol's exports (which makes no sense). --- src/compiler/binder.ts | 4 +- src/compiler/checker.ts | 8 +- .../typeFromPrototypeAssignment2.errors.txt | 47 ++++++ .../typeFromPrototypeAssignment2.symbols | 127 ++++++++++++++ .../typeFromPrototypeAssignment2.types | 156 ++++++++++++++++++ .../salsa/typeFromPrototypeAssignment2.ts | 46 ++++++ 6 files changed, 382 insertions(+), 6 deletions(-) create mode 100644 tests/baselines/reference/typeFromPrototypeAssignment2.errors.txt create mode 100644 tests/baselines/reference/typeFromPrototypeAssignment2.symbols create mode 100644 tests/baselines/reference/typeFromPrototypeAssignment2.types create mode 100644 tests/cases/conformance/salsa/typeFromPrototypeAssignment2.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 81d4857ad073a..74c745df1dc41 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2465,7 +2465,7 @@ namespace ts { node.left.parent = node; node.right.parent = node; const lhs = node.left as PropertyAccessEntityNameExpression; - bindPropertyAssignment(lhs, lhs, /*isPrototypeProperty*/ false); + bindPropertyAssignment(lhs.expression, lhs, /*isPrototypeProperty*/ false); } /** @@ -2522,7 +2522,7 @@ namespace ts { const isToplevel = isBinaryExpression(propertyAccess.parent) ? getParentOfBinaryExpression(propertyAccess.parent).parent.kind === SyntaxKind.SourceFile : propertyAccess.parent.parent.kind === SyntaxKind.SourceFile; - if (!isPrototypeProperty && (!namespaceSymbol || !(namespaceSymbol.flags & SymbolFlags.Namespace)) && isToplevel) { + if (isToplevel && !isPrototypeProperty && (!namespaceSymbol || !(namespaceSymbol.flags & SymbolFlags.Namespace))) { // make symbols or add declarations for intermediate containers const flags = SymbolFlags.Module | SymbolFlags.Assignment; const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.Assignment; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3e9a33007261a..8471f820b40c2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20179,10 +20179,10 @@ namespace ts { (isFunctionDeclaration(decl) && getSymbolOfNode(decl) || isBinaryExpression(decl.parent) && getSymbolOfNode(decl.parent.left) || isVariableDeclaration(decl.parent) && getSymbolOfNode(decl.parent)); - if (assignmentSymbol) { - const prototype = forEach(assignmentSymbol.declarations, getAssignedJSPrototype); - if (prototype) { - return checkExpression(prototype); + if (assignmentSymbol && assignmentSymbol.exports && assignmentSymbol.exports.has("prototype" as __String)) { + const init = getAssignedJSPrototype(assignmentSymbol.exports.get("prototype" as __String)!.valueDeclaration); + if (init) { + return checkExpression(init); } } } diff --git a/tests/baselines/reference/typeFromPrototypeAssignment2.errors.txt b/tests/baselines/reference/typeFromPrototypeAssignment2.errors.txt new file mode 100644 index 0000000000000..d4f66132d44a0 --- /dev/null +++ b/tests/baselines/reference/typeFromPrototypeAssignment2.errors.txt @@ -0,0 +1,47 @@ +tests/cases/conformance/salsa/a.js(28,24): error TS2339: Property 'addon' does not exist on type '{ set: () => void; get(): void; }'. + + +==== tests/cases/conformance/salsa/a.js (1 errors) ==== + // non top-level: + // all references to _map, set, get, addon should be ok + (function container() { + /** @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 + }); + \ No newline at end of file diff --git a/tests/baselines/reference/typeFromPrototypeAssignment2.symbols b/tests/baselines/reference/typeFromPrototypeAssignment2.symbols new file mode 100644 index 0000000000000..25ae9e31c698b --- /dev/null +++ b/tests/baselines/reference/typeFromPrototypeAssignment2.symbols @@ -0,0 +1,127 @@ +=== tests/cases/conformance/salsa/a.js === +// non top-level: +// all references to _map, set, get, addon should be ok +(function container() { +>container : Symbol(container, Decl(a.js, 2, 1)) + + /** @constructor */ + var Multimap = function() { +>Multimap : Symbol(Multimap, Decl(a.js, 4, 7)) + + this._map = {}; +>this._map : Symbol(Multimap._map, Decl(a.js, 4, 31)) +>_map : Symbol(Multimap._map, Decl(a.js, 4, 31)) + + this._map +>this._map : Symbol(Multimap._map, Decl(a.js, 4, 31)) +>_map : Symbol(Multimap._map, Decl(a.js, 4, 31)) + + this.set +>this.set : Symbol(set, Decl(a.js, 12, 26)) +>set : Symbol(set, Decl(a.js, 12, 26)) + + this.get +>this.get : Symbol(get, Decl(a.js, 18, 10)) +>get : Symbol(get, Decl(a.js, 18, 10)) + + this.addon +>this.addon : Symbol(Multimap.addon, Decl(a.js, 25, 5)) +>addon : Symbol(Multimap.addon, Decl(a.js, 25, 5)) + + }; + + Multimap.prototype = { +>Multimap.prototype : Symbol(Multimap.prototype, Decl(a.js, 10, 6)) +>Multimap : Symbol(Multimap, Decl(a.js, 4, 7)) +>prototype : Symbol(Multimap.prototype, Decl(a.js, 10, 6)) + + set: function() { +>set : Symbol(set, Decl(a.js, 12, 26)) + + this._map +>this._map : Symbol(Multimap._map, Decl(a.js, 4, 31)) +>_map : Symbol(Multimap._map, Decl(a.js, 4, 31)) + + this.set +>this.set : Symbol(set, Decl(a.js, 12, 26)) +>set : Symbol(set, Decl(a.js, 12, 26)) + + this.get +>this.get : Symbol(get, Decl(a.js, 18, 10)) +>get : Symbol(get, Decl(a.js, 18, 10)) + + this.addon +>this.addon : Symbol(Multimap.addon, Decl(a.js, 25, 5)) +>addon : Symbol(Multimap.addon, Decl(a.js, 25, 5)) + + }, + get() { +>get : Symbol(get, Decl(a.js, 18, 10)) + + this._map +>this._map : Symbol(Multimap._map, Decl(a.js, 4, 31)) +>_map : Symbol(Multimap._map, Decl(a.js, 4, 31)) + + this.set +>this.set : Symbol(set, Decl(a.js, 12, 26)) +>set : Symbol(set, Decl(a.js, 12, 26)) + + this.get +>this.get : Symbol(get, Decl(a.js, 18, 10)) +>get : Symbol(get, Decl(a.js, 18, 10)) + + this.addon +>this.addon : Symbol(Multimap.addon, Decl(a.js, 25, 5)) +>addon : Symbol(Multimap.addon, Decl(a.js, 25, 5)) + } + } + + Multimap.prototype.addon = function () { +>Multimap.prototype : Symbol(Multimap.addon, Decl(a.js, 25, 5)) +>Multimap : Symbol(Multimap, Decl(a.js, 4, 7)) +>prototype : Symbol(Multimap.prototype, Decl(a.js, 10, 6)) +>addon : Symbol(Multimap.addon, Decl(a.js, 25, 5)) + + this._map +>this._map : Symbol(Multimap._map, Decl(a.js, 4, 31)) +>_map : Symbol(Multimap._map, Decl(a.js, 4, 31)) + + this.set +>this.set : Symbol(set, Decl(a.js, 12, 26)) +>set : Symbol(set, Decl(a.js, 12, 26)) + + this.get +>this.get : Symbol(get, Decl(a.js, 18, 10)) +>get : Symbol(get, Decl(a.js, 18, 10)) + + this.addon +>this.addon : Symbol(Multimap.addon, Decl(a.js, 25, 5)) +>addon : Symbol(Multimap.addon, Decl(a.js, 25, 5)) + } + + var mm = new Multimap(); +>mm : Symbol(mm, Decl(a.js, 34, 7)) +>Multimap : Symbol(Multimap, Decl(a.js, 4, 7)) + + mm._map +>mm._map : Symbol(Multimap._map, Decl(a.js, 4, 31)) +>mm : Symbol(mm, Decl(a.js, 34, 7)) +>_map : Symbol(Multimap._map, Decl(a.js, 4, 31)) + + mm.set +>mm.set : Symbol(set, Decl(a.js, 12, 26)) +>mm : Symbol(mm, Decl(a.js, 34, 7)) +>set : Symbol(set, Decl(a.js, 12, 26)) + + mm.get +>mm.get : Symbol(get, Decl(a.js, 18, 10)) +>mm : Symbol(mm, Decl(a.js, 34, 7)) +>get : Symbol(get, Decl(a.js, 18, 10)) + + mm.addon +>mm.addon : Symbol(Multimap.addon, Decl(a.js, 25, 5)) +>mm : Symbol(mm, Decl(a.js, 34, 7)) +>addon : Symbol(Multimap.addon, Decl(a.js, 25, 5)) + +}); + diff --git a/tests/baselines/reference/typeFromPrototypeAssignment2.types b/tests/baselines/reference/typeFromPrototypeAssignment2.types new file mode 100644 index 0000000000000..d37f421a356e8 --- /dev/null +++ b/tests/baselines/reference/typeFromPrototypeAssignment2.types @@ -0,0 +1,156 @@ +=== tests/cases/conformance/salsa/a.js === +// non top-level: +// all references to _map, set, get, addon should be ok +(function container() { +>(function container() { /** @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}) : () => void +>function container() { /** @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} : () => void +>container : () => void + + /** @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 + +}); + diff --git a/tests/cases/conformance/salsa/typeFromPrototypeAssignment2.ts b/tests/cases/conformance/salsa/typeFromPrototypeAssignment2.ts new file mode 100644 index 0000000000000..68966c923d5c8 --- /dev/null +++ b/tests/cases/conformance/salsa/typeFromPrototypeAssignment2.ts @@ -0,0 +1,46 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: a.js +// @strict: true + +// non top-level: +// all references to _map, set, get, addon should be ok +(function container() { + /** @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 +}); From a9d76b98304b0e257c247b4ef7993d32e30e05bb Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Sun, 16 Sep 2018 19:46:33 -0700 Subject: [PATCH 2/2] Refactor per PR suggestion --- src/compiler/checker.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8471f820b40c2..3b031198b38b6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20173,18 +20173,15 @@ namespace ts { assigned || inferred; } - function getAssignedClassType(symbol: Symbol) { + function getAssignedClassType(symbol: Symbol): Type | undefined { const decl = symbol.valueDeclaration; const assignmentSymbol = decl && decl.parent && (isFunctionDeclaration(decl) && getSymbolOfNode(decl) || isBinaryExpression(decl.parent) && getSymbolOfNode(decl.parent.left) || isVariableDeclaration(decl.parent) && getSymbolOfNode(decl.parent)); - if (assignmentSymbol && assignmentSymbol.exports && assignmentSymbol.exports.has("prototype" as __String)) { - const init = getAssignedJSPrototype(assignmentSymbol.exports.get("prototype" as __String)!.valueDeclaration); - if (init) { - return checkExpression(init); - } - } + const prototype = assignmentSymbol && assignmentSymbol.exports && assignmentSymbol.exports.get("prototype" as __String); + const init = prototype && getAssignedJSPrototype(prototype.valueDeclaration); + return init ? checkExpression(init) : undefined; } function getAssignedJSPrototype(node: Node) {