Skip to content

Commit d0159a8

Browse files
authored
Fix late bound method name assignment, added tests (#43344)
* Fix late bound method name assignment, added tests * Refactor bindDynamicallyNamedthisPropertyAssignment * PR comments * Rollback allowJscheck fix
1 parent 971133d commit d0159a8

File tree

6 files changed

+93
-8
lines changed

6 files changed

+93
-8
lines changed

src/compiler/binder.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -410,13 +410,15 @@ namespace ts {
410410
* @param includes - The SymbolFlags that node has in addition to its declaration type (eg: export, ambient, etc.)
411411
* @param excludes - The flags which node cannot be declared alongside in a symbol table. Used to report forbidden declarations.
412412
*/
413-
function declareSymbol(symbolTable: SymbolTable, parent: Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean): Symbol {
414-
Debug.assert(!hasDynamicName(node));
413+
function declareSymbol(symbolTable: SymbolTable, parent: Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean, isComputedName?: boolean): Symbol {
414+
Debug.assert(isComputedName || !hasDynamicName(node));
415415

416416
const isDefaultExport = hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node) && node.name.escapedText === "default";
417417

418418
// The exported symbol for an export default function/class node is always named "default"
419-
const name = isDefaultExport && parent ? InternalSymbolName.Default : getDeclarationName(node);
419+
const name = isComputedName ? InternalSymbolName.Computed
420+
: isDefaultExport && parent ? InternalSymbolName.Default
421+
: getDeclarationName(node);
420422

421423
let symbol: Symbol | undefined;
422424
if (name === undefined) {
@@ -2929,7 +2931,7 @@ namespace ts {
29292931
constructorSymbol.members = constructorSymbol.members || createSymbolTable();
29302932
// It's acceptable for multiple 'this' assignments of the same identifier to occur
29312933
if (hasDynamicName(node)) {
2932-
bindDynamicallyNamedThisPropertyAssignment(node, constructorSymbol);
2934+
bindDynamicallyNamedThisPropertyAssignment(node, constructorSymbol, constructorSymbol.members);
29332935
}
29342936
else {
29352937
declareSymbol(constructorSymbol.members, constructorSymbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property);
@@ -2948,7 +2950,7 @@ namespace ts {
29482950
const containingClass = thisContainer.parent;
29492951
const symbolTable = hasSyntacticModifier(thisContainer, ModifierFlags.Static) ? containingClass.symbol.exports! : containingClass.symbol.members!;
29502952
if (hasDynamicName(node)) {
2951-
bindDynamicallyNamedThisPropertyAssignment(node, containingClass.symbol);
2953+
bindDynamicallyNamedThisPropertyAssignment(node, containingClass.symbol, symbolTable);
29522954
}
29532955
else {
29542956
declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.None, /*isReplaceableByMethod*/ true);
@@ -2972,8 +2974,8 @@ namespace ts {
29722974
}
29732975
}
29742976

2975-
function bindDynamicallyNamedThisPropertyAssignment(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol) {
2976-
bindAnonymousDeclaration(node, SymbolFlags.Property, InternalSymbolName.Computed);
2977+
function bindDynamicallyNamedThisPropertyAssignment(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol, symbolTable: SymbolTable) {
2978+
declareSymbol(symbolTable, symbol, node, SymbolFlags.Property, SymbolFlags.None, /*isReplaceableByMethod*/ true, /*isComputedName*/ true);
29772979
addLateBoundAssignmentDeclarationToSymbol(node, symbol);
29782980
}
29792981

src/compiler/checker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10243,7 +10243,7 @@ namespace ts {
1024310243
if (!symbol.declarations) {
1024410244
symbol.declarations = [member];
1024510245
}
10246-
else {
10246+
else if(!member.symbol.isReplaceableByMethod) {
1024710247
symbol.declarations.push(member);
1024810248
}
1024910249
if (symbolFlags & SymbolFlags.Value) {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//// [lateBoundMethodNameAssigmentJS.js]
2+
const _symbol = Symbol("_sym");
3+
export class MyClass {
4+
constructor() {
5+
this[_symbol] = this[_symbol].bind(this);
6+
}
7+
8+
async [_symbol]() { }
9+
}
10+
11+
12+
13+
//// [lateBoundMethodNameAssigmentJS.d.ts]
14+
export class MyClass {
15+
[_symbol]: any;
16+
}
17+
declare const _symbol: unique symbol;
18+
export {};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
=== tests/cases/compiler/lateBoundMethodNameAssigmentJS.js ===
2+
const _symbol = Symbol("_sym");
3+
>_symbol : Symbol(_symbol, Decl(lateBoundMethodNameAssigmentJS.js, 0, 5))
4+
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
5+
6+
export class MyClass {
7+
>MyClass : Symbol(MyClass, Decl(lateBoundMethodNameAssigmentJS.js, 0, 31))
8+
9+
constructor() {
10+
this[_symbol] = this[_symbol].bind(this);
11+
>this : Symbol(MyClass, Decl(lateBoundMethodNameAssigmentJS.js, 0, 31))
12+
>_symbol : Symbol(_symbol, Decl(lateBoundMethodNameAssigmentJS.js, 0, 5))
13+
>this : Symbol(MyClass, Decl(lateBoundMethodNameAssigmentJS.js, 0, 31))
14+
>_symbol : Symbol(_symbol, Decl(lateBoundMethodNameAssigmentJS.js, 0, 5))
15+
>this : Symbol(MyClass, Decl(lateBoundMethodNameAssigmentJS.js, 0, 31))
16+
}
17+
18+
async [_symbol]() { }
19+
>[_symbol] : Symbol(MyClass[_symbol], Decl(lateBoundMethodNameAssigmentJS.js, 4, 5))
20+
>_symbol : Symbol(_symbol, Decl(lateBoundMethodNameAssigmentJS.js, 0, 5))
21+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=== tests/cases/compiler/lateBoundMethodNameAssigmentJS.js ===
2+
const _symbol = Symbol("_sym");
3+
>_symbol : unique symbol
4+
>Symbol("_sym") : unique symbol
5+
>Symbol : SymbolConstructor
6+
>"_sym" : "_sym"
7+
8+
export class MyClass {
9+
>MyClass : MyClass
10+
11+
constructor() {
12+
this[_symbol] = this[_symbol].bind(this);
13+
>this[_symbol] = this[_symbol].bind(this) : error
14+
>this[_symbol] : error
15+
>this : this
16+
>_symbol : unique symbol
17+
>this[_symbol].bind(this) : error
18+
>this[_symbol].bind : error
19+
>this[_symbol] : any
20+
>this : this
21+
>_symbol : unique symbol
22+
>bind : any
23+
>this : this
24+
}
25+
26+
async [_symbol]() { }
27+
>[_symbol] : error
28+
>_symbol : unique symbol
29+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// @allowJs: true
2+
// @checkJs: true
3+
// @emitDeclarationOnly: true
4+
// @strict: true
5+
// @target: es6
6+
// @declaration: true
7+
// @filename: lateBoundMethodNameAssigmentJS.js
8+
const _symbol = Symbol("_sym");
9+
export class MyClass {
10+
constructor() {
11+
this[_symbol] = this[_symbol].bind(this);
12+
}
13+
14+
async [_symbol]() { }
15+
}

0 commit comments

Comments
 (0)