Allow JS multiple declarations of ctor properties

When a property is declared in the constructor and on the prototype of
an ES6 class, the property's symbol is discarded in favour of the
method's symbol. That because the usual use for this pattern is to bind
an instance function: `this.m = this.m.bind(this)`. In this case the
type you want really is the method's type.
This commit is contained in:
Nathan Shively-Sanders
2016-08-03 10:33:10 -07:00
parent 0eeb9cbd0c
commit 8c01efba04
6 changed files with 184 additions and 30 deletions
+34 -26
View File
@@ -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) {
+1
View File
@@ -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 */
@@ -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;
@@ -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))
@@ -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
@@ -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;