From d0159a88914f0639230741b2a5176f012bc0b8bb Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Wed, 16 Jun 2021 12:46:00 -0700 Subject: [PATCH] Fix late bound method name assignment, added tests (#43344) * Fix late bound method name assignment, added tests * Refactor bindDynamicallyNamedthisPropertyAssignment * PR comments * Rollback allowJscheck fix --- src/compiler/binder.ts | 16 +++++----- src/compiler/checker.ts | 2 +- .../lateBoundMethodNameAssigmentJS.js | 18 ++++++++++++ .../lateBoundMethodNameAssigmentJS.symbols | 21 ++++++++++++++ .../lateBoundMethodNameAssigmentJS.types | 29 +++++++++++++++++++ .../lateBoundMethodNameAssigmentJS.ts | 15 ++++++++++ 6 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/lateBoundMethodNameAssigmentJS.js create mode 100644 tests/baselines/reference/lateBoundMethodNameAssigmentJS.symbols create mode 100644 tests/baselines/reference/lateBoundMethodNameAssigmentJS.types create mode 100644 tests/cases/compiler/lateBoundMethodNameAssigmentJS.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 78bca28c26b..a060076951d 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -410,13 +410,15 @@ namespace ts { * @param includes - The SymbolFlags that node has in addition to its declaration type (eg: export, ambient, etc.) * @param excludes - The flags which node cannot be declared alongside in a symbol table. Used to report forbidden declarations. */ - function declareSymbol(symbolTable: SymbolTable, parent: Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean): Symbol { - Debug.assert(!hasDynamicName(node)); + function declareSymbol(symbolTable: SymbolTable, parent: Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean, isComputedName?: boolean): Symbol { + Debug.assert(isComputedName || !hasDynamicName(node)); const isDefaultExport = hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node) && node.name.escapedText === "default"; // The exported symbol for an export default function/class node is always named "default" - const name = isDefaultExport && parent ? InternalSymbolName.Default : getDeclarationName(node); + const name = isComputedName ? InternalSymbolName.Computed + : isDefaultExport && parent ? InternalSymbolName.Default + : getDeclarationName(node); let symbol: Symbol | undefined; if (name === undefined) { @@ -2929,7 +2931,7 @@ namespace ts { constructorSymbol.members = constructorSymbol.members || createSymbolTable(); // It's acceptable for multiple 'this' assignments of the same identifier to occur if (hasDynamicName(node)) { - bindDynamicallyNamedThisPropertyAssignment(node, constructorSymbol); + bindDynamicallyNamedThisPropertyAssignment(node, constructorSymbol, constructorSymbol.members); } else { declareSymbol(constructorSymbol.members, constructorSymbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); @@ -2948,7 +2950,7 @@ namespace ts { const containingClass = thisContainer.parent; const symbolTable = hasSyntacticModifier(thisContainer, ModifierFlags.Static) ? containingClass.symbol.exports! : containingClass.symbol.members!; if (hasDynamicName(node)) { - bindDynamicallyNamedThisPropertyAssignment(node, containingClass.symbol); + bindDynamicallyNamedThisPropertyAssignment(node, containingClass.symbol, symbolTable); } else { declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.None, /*isReplaceableByMethod*/ true); @@ -2972,8 +2974,8 @@ namespace ts { } } - function bindDynamicallyNamedThisPropertyAssignment(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol) { - bindAnonymousDeclaration(node, SymbolFlags.Property, InternalSymbolName.Computed); + function bindDynamicallyNamedThisPropertyAssignment(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol, symbolTable: SymbolTable) { + declareSymbol(symbolTable, symbol, node, SymbolFlags.Property, SymbolFlags.None, /*isReplaceableByMethod*/ true, /*isComputedName*/ true); addLateBoundAssignmentDeclarationToSymbol(node, symbol); } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 533bedc70a1..edc9e429eb3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10243,7 +10243,7 @@ namespace ts { if (!symbol.declarations) { symbol.declarations = [member]; } - else { + else if(!member.symbol.isReplaceableByMethod) { symbol.declarations.push(member); } if (symbolFlags & SymbolFlags.Value) { diff --git a/tests/baselines/reference/lateBoundMethodNameAssigmentJS.js b/tests/baselines/reference/lateBoundMethodNameAssigmentJS.js new file mode 100644 index 00000000000..d5aec34333d --- /dev/null +++ b/tests/baselines/reference/lateBoundMethodNameAssigmentJS.js @@ -0,0 +1,18 @@ +//// [lateBoundMethodNameAssigmentJS.js] +const _symbol = Symbol("_sym"); +export class MyClass { + constructor() { + this[_symbol] = this[_symbol].bind(this); + } + + async [_symbol]() { } +} + + + +//// [lateBoundMethodNameAssigmentJS.d.ts] +export class MyClass { + [_symbol]: any; +} +declare const _symbol: unique symbol; +export {}; diff --git a/tests/baselines/reference/lateBoundMethodNameAssigmentJS.symbols b/tests/baselines/reference/lateBoundMethodNameAssigmentJS.symbols new file mode 100644 index 00000000000..1ffa8b71ccc --- /dev/null +++ b/tests/baselines/reference/lateBoundMethodNameAssigmentJS.symbols @@ -0,0 +1,21 @@ +=== tests/cases/compiler/lateBoundMethodNameAssigmentJS.js === +const _symbol = Symbol("_sym"); +>_symbol : Symbol(_symbol, Decl(lateBoundMethodNameAssigmentJS.js, 0, 5)) +>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) + +export class MyClass { +>MyClass : Symbol(MyClass, Decl(lateBoundMethodNameAssigmentJS.js, 0, 31)) + + constructor() { + this[_symbol] = this[_symbol].bind(this); +>this : Symbol(MyClass, Decl(lateBoundMethodNameAssigmentJS.js, 0, 31)) +>_symbol : Symbol(_symbol, Decl(lateBoundMethodNameAssigmentJS.js, 0, 5)) +>this : Symbol(MyClass, Decl(lateBoundMethodNameAssigmentJS.js, 0, 31)) +>_symbol : Symbol(_symbol, Decl(lateBoundMethodNameAssigmentJS.js, 0, 5)) +>this : Symbol(MyClass, Decl(lateBoundMethodNameAssigmentJS.js, 0, 31)) + } + + async [_symbol]() { } +>[_symbol] : Symbol(MyClass[_symbol], Decl(lateBoundMethodNameAssigmentJS.js, 4, 5)) +>_symbol : Symbol(_symbol, Decl(lateBoundMethodNameAssigmentJS.js, 0, 5)) +} diff --git a/tests/baselines/reference/lateBoundMethodNameAssigmentJS.types b/tests/baselines/reference/lateBoundMethodNameAssigmentJS.types new file mode 100644 index 00000000000..8b0217b2783 --- /dev/null +++ b/tests/baselines/reference/lateBoundMethodNameAssigmentJS.types @@ -0,0 +1,29 @@ +=== tests/cases/compiler/lateBoundMethodNameAssigmentJS.js === +const _symbol = Symbol("_sym"); +>_symbol : unique symbol +>Symbol("_sym") : unique symbol +>Symbol : SymbolConstructor +>"_sym" : "_sym" + +export class MyClass { +>MyClass : MyClass + + constructor() { + this[_symbol] = this[_symbol].bind(this); +>this[_symbol] = this[_symbol].bind(this) : error +>this[_symbol] : error +>this : this +>_symbol : unique symbol +>this[_symbol].bind(this) : error +>this[_symbol].bind : error +>this[_symbol] : any +>this : this +>_symbol : unique symbol +>bind : any +>this : this + } + + async [_symbol]() { } +>[_symbol] : error +>_symbol : unique symbol +} diff --git a/tests/cases/compiler/lateBoundMethodNameAssigmentJS.ts b/tests/cases/compiler/lateBoundMethodNameAssigmentJS.ts new file mode 100644 index 00000000000..2ed5e01718f --- /dev/null +++ b/tests/cases/compiler/lateBoundMethodNameAssigmentJS.ts @@ -0,0 +1,15 @@ +// @allowJs: true +// @checkJs: true +// @emitDeclarationOnly: true +// @strict: true +// @target: es6 +// @declaration: true +// @filename: lateBoundMethodNameAssigmentJS.js +const _symbol = Symbol("_sym"); +export class MyClass { + constructor() { + this[_symbol] = this[_symbol].bind(this); + } + + async [_symbol]() { } +} \ No newline at end of file