diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 8d8666a67ab..6f0def32702 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -298,8 +298,10 @@ namespace ts { const name = isDefaultExport && parent ? "default" : getDeclarationName(node); let symbol: Symbol; - if (name !== undefined) { - + if (name === undefined) { + symbol = createSymbol(SymbolFlags.None, "__missing"); + } + else { // Check and see if the symbol table already has a symbol with this name. If not, // create a new symbol with this name and add it to the table. Note that we don't // give the new symbol any flags *yet*. This ensures that it will not conflict @@ -326,34 +328,38 @@ namespace ts { classifiableNames[name] = name; } - if (symbol.flags & excludes) { - if (node.name) { - node.name.parent = node; + else if (symbol.flags & excludes) { + if (symbol.isDiscardable) { + // Javascript constructor-declared symbols can be discarded in favor of + // prototype symbols like methods. + symbol = symbolTable[name] = createSymbol(SymbolFlags.None, name); } - - // Report errors every position with duplicate declaration - // Report errors on previous encountered declarations - let message = symbol.flags & SymbolFlags.BlockScopedVariable - ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 - : Diagnostics.Duplicate_identifier_0; - - forEach(symbol.declarations, declaration => { - if (declaration.flags & NodeFlags.Default) { - message = Diagnostics.A_module_cannot_have_multiple_default_exports; + else { + if (node.name) { + node.name.parent = node; } - }); - forEach(symbol.declarations, declaration => { - file.bindDiagnostics.push(createDiagnosticForNode(declaration.name || declaration, message, getDisplayName(declaration))); - }); - file.bindDiagnostics.push(createDiagnosticForNode(node.name || node, message, getDisplayName(node))); + // Report errors every position with duplicate declaration + // Report errors on previous encountered declarations + let message = symbol.flags & SymbolFlags.BlockScopedVariable + ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 + : Diagnostics.Duplicate_identifier_0; - symbol = createSymbol(SymbolFlags.None, name); + forEach(symbol.declarations, declaration => { + if (declaration.flags & NodeFlags.Default) { + message = Diagnostics.A_module_cannot_have_multiple_default_exports; + } + }); + + forEach(symbol.declarations, declaration => { + file.bindDiagnostics.push(createDiagnosticForNode(declaration.name || declaration, message, getDisplayName(declaration))); + }); + file.bindDiagnostics.push(createDiagnosticForNode(node.name || node, message, getDisplayName(node))); + + symbol = createSymbol(SymbolFlags.None, name); + } } } - else { - symbol = createSymbol(SymbolFlags.None, "__missing"); - } addDeclarationToSymbol(symbol, node, includes); symbol.parent = parent; @@ -1967,7 +1973,7 @@ namespace ts { } function bindThisPropertyAssignment(node: BinaryExpression) { - // Declare a 'member' in case it turns out the container was an ES5 class or ES6 constructor + // Declare a 'member' if the container is an ES5 class or ES6 constructor let assignee: Node; if (container.kind === SyntaxKind.FunctionDeclaration || container.kind === SyntaxKind.FunctionExpression) { assignee = container; @@ -1980,7 +1986,9 @@ namespace ts { } assignee.symbol.members = assignee.symbol.members || {}; // It's acceptable for multiple 'this' assignments of the same identifier to occur - declareSymbol(assignee.symbol.members, assignee.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); + // AND it can be overwritten by subsequent method declarations + let symbol = declareSymbol(assignee.symbol.members, assignee.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); + symbol.isDiscardable = true; } function bindPrototypePropertyAssignment(node: BinaryExpression) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 072209aa496..4c08d7a487c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2137,6 +2137,7 @@ namespace ts { /* @internal */ exportSymbol?: Symbol; // Exported symbol associated with this symbol /* @internal */ constEnumOnlyModule?: boolean; // True if module contains only const enums or other modules with only const enums /* @internal */ isReferenced?: boolean; // True if the symbol is referenced elsewhere + /* @internal */ isDiscardable?: boolean;// True if a Javascript class property can be overwritten by a method } /* @internal */ diff --git a/tests/baselines/reference/multipleDeclarations.js b/tests/baselines/reference/multipleDeclarations.js index 5f5ac29e909..e5aae416663 100644 --- a/tests/baselines/reference/multipleDeclarations.js +++ b/tests/baselines/reference/multipleDeclarations.js @@ -5,7 +5,22 @@ function C() { } C.prototype.m = function() { this.nothing(); -}; +} + +class X { + constructor() { + this.m = this.m.bind(this); + this.mistake = 'frankly, complete nonsense'; + } + m() { + } + mistake() { + } +} +let x = new X(); +X.prototype.mistake = false; +x.m(); +x.mistake; //// [output.js] @@ -15,3 +30,18 @@ function C() { C.prototype.m = function () { this.nothing(); }; +var X = (function () { + function X() { + this.m = this.m.bind(this); + this.mistake = 'frankly, complete nonsense'; + } + X.prototype.m = function () { + }; + X.prototype.mistake = function () { + }; + return X; +}()); +var x = new X(); +X.prototype.mistake = false; +x.m(); +x.mistake; diff --git a/tests/baselines/reference/multipleDeclarations.symbols b/tests/baselines/reference/multipleDeclarations.symbols index a256943dd50..7e4bcd53c8a 100644 --- a/tests/baselines/reference/multipleDeclarations.symbols +++ b/tests/baselines/reference/multipleDeclarations.symbols @@ -14,6 +14,51 @@ C.prototype.m = function() { this.nothing(); >this : Symbol(C, Decl(input.js, 0, 0)) +} -}; +class X { +>X : Symbol(X, Decl(input.js, 6, 1)) + + constructor() { + this.m = this.m.bind(this); +>this.m : Symbol(X.m, Decl(input.js, 12, 5)) +>this : Symbol(X, Decl(input.js, 6, 1)) +>m : Symbol(X.m, Decl(input.js, 9, 19)) +>this.m.bind : Symbol(Function.bind, Decl(lib.d.ts, --, --)) +>this.m : Symbol(X.m, Decl(input.js, 12, 5)) +>this : Symbol(X, Decl(input.js, 6, 1)) +>m : Symbol(X.m, Decl(input.js, 12, 5)) +>bind : Symbol(Function.bind, Decl(lib.d.ts, --, --)) +>this : Symbol(X, Decl(input.js, 6, 1)) + + this.mistake = 'frankly, complete nonsense'; +>this.mistake : Symbol(X.mistake, Decl(input.js, 14, 5)) +>this : Symbol(X, Decl(input.js, 6, 1)) +>mistake : Symbol(X.mistake, Decl(input.js, 10, 35)) + } + m() { +>m : Symbol(X.m, Decl(input.js, 12, 5)) + } + mistake() { +>mistake : Symbol(X.mistake, Decl(input.js, 14, 5)) + } +} +let x = new X(); +>x : Symbol(x, Decl(input.js, 18, 3)) +>X : Symbol(X, Decl(input.js, 6, 1)) + +X.prototype.mistake = false; +>X.prototype.mistake : Symbol(X.mistake, Decl(input.js, 14, 5)) +>X : Symbol(X, Decl(input.js, 6, 1)) +>prototype : Symbol(X.prototype) + +x.m(); +>x.m : Symbol(X.m, Decl(input.js, 12, 5)) +>x : Symbol(x, Decl(input.js, 18, 3)) +>m : Symbol(X.m, Decl(input.js, 12, 5)) + +x.mistake; +>x.mistake : Symbol(X.mistake, Decl(input.js, 14, 5)) +>x : Symbol(x, Decl(input.js, 18, 3)) +>mistake : Symbol(X.mistake, Decl(input.js, 14, 5)) diff --git a/tests/baselines/reference/multipleDeclarations.types b/tests/baselines/reference/multipleDeclarations.types index 7c0a3de70c9..2a7cac18205 100644 --- a/tests/baselines/reference/multipleDeclarations.types +++ b/tests/baselines/reference/multipleDeclarations.types @@ -24,6 +24,61 @@ C.prototype.m = function() { >this.nothing : any >this : { m: () => void; } >nothing : any +} -}; +class X { +>X : X + + constructor() { + this.m = this.m.bind(this); +>this.m = this.m.bind(this) : any +>this.m : () => void +>this : this +>m : () => void +>this.m.bind(this) : any +>this.m.bind : (this: Function, thisArg: any, ...argArray: any[]) => any +>this.m : () => void +>this : this +>m : () => void +>bind : (this: Function, thisArg: any, ...argArray: any[]) => any +>this : this + + this.mistake = 'frankly, complete nonsense'; +>this.mistake = 'frankly, complete nonsense' : string +>this.mistake : () => void +>this : this +>mistake : () => void +>'frankly, complete nonsense' : string + } + m() { +>m : () => void + } + mistake() { +>mistake : () => void + } +} +let x = new X(); +>x : X +>new X() : X +>X : typeof X + +X.prototype.mistake = false; +>X.prototype.mistake = false : boolean +>X.prototype.mistake : () => void +>X.prototype : X +>X : typeof X +>prototype : X +>mistake : () => void +>false : boolean + +x.m(); +>x.m() : void +>x.m : () => void +>x : X +>m : () => void + +x.mistake; +>x.mistake : () => void +>x : X +>mistake : () => void diff --git a/tests/cases/conformance/salsa/multipleDeclarations.ts b/tests/cases/conformance/salsa/multipleDeclarations.ts index 6899be2ec89..9ead1eeb0a5 100644 --- a/tests/cases/conformance/salsa/multipleDeclarations.ts +++ b/tests/cases/conformance/salsa/multipleDeclarations.ts @@ -7,4 +7,19 @@ function C() { } C.prototype.m = function() { this.nothing(); -}; +} + +class X { + constructor() { + this.m = this.m.bind(this); + this.mistake = 'frankly, complete nonsense'; + } + m() { + } + mistake() { + } +} +let x = new X(); +X.prototype.mistake = false; +x.m(); +x.mistake;