mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
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:
+34
-26
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user