Skip to content

Commit 3c652cf

Browse files
committed
Support some late-bound special property assignments
1 parent f41472b commit 3c652cf

17 files changed

+297
-45
lines changed

src/compiler/binder.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2521,7 +2521,14 @@ namespace ts {
25212521
// Declare a 'member' if the container is an ES5 class or ES6 constructor
25222522
constructorSymbol.members = constructorSymbol.members || createSymbolTable();
25232523
// It's acceptable for multiple 'this' assignments of the same identifier to occur
2524-
declareSymbol(constructorSymbol.members, constructorSymbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property);
2524+
if (hasDynamicName(node)) {
2525+
bindAnonymousDeclaration(node, SymbolFlags.Property, InternalSymbolName.Computed);
2526+
const members = constructorSymbol.assignmentDeclarationMembers || (constructorSymbol.assignmentDeclarationMembers = createMap());
2527+
members.set("" + getNodeId(node), node);
2528+
}
2529+
else {
2530+
declareSymbol(constructorSymbol.members, constructorSymbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property);
2531+
}
25252532
addDeclarationToSymbol(constructorSymbol, constructorSymbol.valueDeclaration, SymbolFlags.Class);
25262533
}
25272534
break;
@@ -2535,7 +2542,14 @@ namespace ts {
25352542
// Bind this property to the containing class
25362543
const containingClass = thisContainer.parent;
25372544
const symbolTable = hasModifier(thisContainer, ModifierFlags.Static) ? containingClass.symbol.exports! : containingClass.symbol.members!;
2538-
declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property, SymbolFlags.None, /*isReplaceableByMethod*/ true);
2545+
if (hasDynamicName(node)) {
2546+
bindAnonymousDeclaration(node, SymbolFlags.Property, InternalSymbolName.Computed);
2547+
const members = containingClass.symbol.assignmentDeclarationMembers || (containingClass.symbol.assignmentDeclarationMembers = createMap());
2548+
members.set("" + getNodeId(node), node);
2549+
}
2550+
else {
2551+
declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property, SymbolFlags.None, /*isReplaceableByMethod*/ true);
2552+
}
25392553
break;
25402554
case SyntaxKind.SourceFile:
25412555
// this.property = assignment in a source file -- declare symbol in exports for a module, in locals for a script
@@ -2621,7 +2635,17 @@ namespace ts {
26212635
bindExportsPropertyAssignment(node);
26222636
}
26232637
else {
2624-
bindStaticPropertyAssignment(lhs);
2638+
if (hasDynamicName(node)) {
2639+
bindAnonymousDeclaration(node, SymbolFlags.Property | SymbolFlags.Assignment, InternalSymbolName.Computed);
2640+
const sym = bindPotentiallyMissingNamespaces(parentSymbol, lhs.expression, isTopLevelNamespaceAssignment(lhs), /*isPrototype*/ false, /*containerIsClass*/ false);
2641+
if (sym) {
2642+
const members = sym.assignmentDeclarationMembers || (sym.assignmentDeclarationMembers = createMap());
2643+
members.set("" + getNodeId(node), node);
2644+
}
2645+
}
2646+
else {
2647+
bindStaticPropertyAssignment(lhs);
2648+
}
26252649
}
26262650
}
26272651

src/compiler/checker.ts

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2817,7 +2817,7 @@ namespace ts {
28172817
}
28182818

28192819
function getExportsOfSymbol(symbol: Symbol): SymbolTable {
2820-
return symbol.flags & SymbolFlags.Class ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedExports) :
2820+
return symbol.flags & SymbolFlags.LateBindingContainer ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedExports) :
28212821
symbol.flags & SymbolFlags.Module ? getExportsOfModule(symbol) :
28222822
symbol.exports || emptySymbols;
28232823
}
@@ -4118,7 +4118,15 @@ namespace ts {
41184118
if (context.tracker.trackSymbol && getCheckFlags(propertySymbol) & CheckFlags.Late) {
41194119
const decl = first(propertySymbol.declarations);
41204120
if (hasLateBindableName(decl)) {
4121-
trackComputedName(decl.name, saveEnclosingDeclaration, context);
4121+
if (!isBinaryExpression(decl)) {
4122+
trackComputedName(decl.name.expression, saveEnclosingDeclaration, context);
4123+
}
4124+
else {
4125+
const name = getNameOfDeclaration(decl);
4126+
if (name && isElementAccessExpression(name) && isPropertyAccessEntityNameExpression(name.argumentExpression)) {
4127+
trackComputedName(name.argumentExpression, saveEnclosingDeclaration, context);
4128+
}
4129+
}
41224130
}
41234131
}
41244132
const propertyName = symbolToName(propertySymbol, context, SymbolFlags.Value, /*expectsIdentifier*/ true);
@@ -4324,7 +4332,7 @@ namespace ts {
43244332
return <BindingName>elideInitializerAndSetEmitFlags(node);
43254333
function elideInitializerAndSetEmitFlags(node: Node): Node {
43264334
if (context.tracker.trackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) {
4327-
trackComputedName(node, context.enclosingDeclaration, context);
4335+
trackComputedName(node.expression, context.enclosingDeclaration, context);
43284336
}
43294337
const visited = visitEachChild(node, elideInitializerAndSetEmitFlags, nullTransformationContext, /*nodesVisitor*/ undefined, elideInitializerAndSetEmitFlags)!;
43304338
const clone = nodeIsSynthesized(visited) ? visited : getSynthesizedClone(visited);
@@ -4336,10 +4344,10 @@ namespace ts {
43364344
}
43374345
}
43384346

4339-
function trackComputedName(node: LateBoundName, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) {
4347+
function trackComputedName(accessExpression: EntityNameOrEntityNameExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) {
43404348
if (!context.tracker.trackSymbol) return;
43414349
// get symbol of the first identifier of the entityName
4342-
const firstIdentifier = getFirstIdentifier(node.expression);
4350+
const firstIdentifier = getFirstIdentifier(accessExpression);
43434351
const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true);
43444352
if (name) {
43454353
context.tracker.trackSymbol(name, enclosingDeclaration, SymbolFlags.Value);
@@ -5831,6 +5839,9 @@ namespace ts {
58315839
if (declaration.kind === SyntaxKind.ExportAssignment) {
58325840
type = widenTypeForVariableLikeDeclaration(checkExpressionCached((<ExportAssignment>declaration).expression), declaration);
58335841
}
5842+
else if (isBinaryExpression(declaration)) {
5843+
type = getWidenedTypeForAssignmentDeclaration(symbol);
5844+
}
58345845
else if (isInJSFile(declaration) &&
58355846
(isCallExpression(declaration) || isBinaryExpression(declaration) || isPropertyAccessExpression(declaration) && isBinaryExpression(declaration.parent))) {
58365847
type = getWidenedTypeForAssignmentDeclaration(symbol);
@@ -6836,9 +6847,12 @@ namespace ts {
68366847
* - The type of its expression is a string or numeric literal type, or is a `unique symbol` type.
68376848
*/
68386849
function isLateBindableName(node: DeclarationName): node is LateBoundName {
6839-
return isComputedPropertyName(node)
6840-
&& isEntityNameExpression(node.expression)
6841-
&& isTypeUsableAsPropertyName(checkComputedPropertyName(node));
6850+
if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) {
6851+
return false;
6852+
}
6853+
const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression;
6854+
return isEntityNameExpression(expr)
6855+
&& isTypeUsableAsPropertyName(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached(expr));
68426856
}
68436857

68446858
function isLateBoundName(name: __String): boolean {
@@ -6850,7 +6864,7 @@ namespace ts {
68506864
/**
68516865
* Indicates whether a declaration has a late-bindable dynamic name.
68526866
*/
6853-
function hasLateBindableName(node: Declaration): node is LateBoundDeclaration {
6867+
function hasLateBindableName(node: Declaration): node is LateBoundDeclaration | BinaryExpression {
68546868
const name = getNameOfDeclaration(node);
68556869
return !!name && isLateBindableName(name);
68566870
}
@@ -6887,7 +6901,7 @@ namespace ts {
68876901
* late-bound members that `addDeclarationToSymbol` in binder.ts performs for early-bound
68886902
* members.
68896903
*/
6890-
function addDeclarationToLateBoundSymbol(symbol: Symbol, member: LateBoundDeclaration, symbolFlags: SymbolFlags) {
6904+
function addDeclarationToLateBoundSymbol(symbol: Symbol, member: LateBoundDeclaration | BinaryExpression, symbolFlags: SymbolFlags) {
68916905
Debug.assert(!!(getCheckFlags(symbol) & CheckFlags.Late), "Expected a late-bound symbol.");
68926906
symbol.flags |= symbolFlags;
68936907
getSymbolLinks(member.symbol).lateSymbol = symbol;
@@ -6932,14 +6946,15 @@ namespace ts {
69326946
* @param lateSymbols The late-bound symbols of the parent.
69336947
* @param decl The member to bind.
69346948
*/
6935-
function lateBindMember(parent: Symbol, earlySymbols: SymbolTable | undefined, lateSymbols: SymbolTable, decl: LateBoundDeclaration) {
6949+
function lateBindMember(parent: Symbol, earlySymbols: SymbolTable | undefined, lateSymbols: SymbolTable, decl: LateBoundDeclaration | BinaryExpression) {
69366950
Debug.assert(!!decl.symbol, "The member is expected to have a symbol.");
69376951
const links = getNodeLinks(decl);
69386952
if (!links.resolvedSymbol) {
69396953
// In the event we attempt to resolve the late-bound name of this member recursively,
69406954
// fall back to the early-bound name of this member.
69416955
links.resolvedSymbol = decl.symbol;
6942-
const type = checkComputedPropertyName(decl.name);
6956+
const declName = isBinaryExpression(decl) ? (getNameOfDeclaration(decl) as ElementAccessExpression) : decl.name;
6957+
const type = isElementAccessExpression(declName) ? checkExpressionCached(declName.argumentExpression) : checkComputedPropertyName(declName);
69436958
if (isTypeUsableAsPropertyName(type)) {
69446959
const memberName = getPropertyNameFromType(type);
69456960
const symbolFlags = decl.symbol.flags;
@@ -6956,9 +6971,9 @@ namespace ts {
69566971
// If we have an existing early-bound member, combine its declarations so that we can
69576972
// report an error at each declaration.
69586973
const declarations = earlySymbol ? concatenate(earlySymbol.declarations, lateSymbol.declarations) : lateSymbol.declarations;
6959-
const name = !(type.flags & TypeFlags.UniqueESSymbol) && unescapeLeadingUnderscores(memberName) || declarationNameToString(decl.name);
6974+
const name = !(type.flags & TypeFlags.UniqueESSymbol) && unescapeLeadingUnderscores(memberName) || declarationNameToString(declName);
69606975
forEach(declarations, declaration => error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Property_0_was_also_declared_here, name));
6961-
error(decl.name || decl, Diagnostics.Duplicate_property_0, name);
6976+
error(declName || decl, Diagnostics.Duplicate_property_0, name);
69626977
lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late);
69636978
}
69646979
lateSymbol.nameType = type;
@@ -7000,6 +7015,20 @@ namespace ts {
70007015
}
70017016
}
70027017
}
7018+
const assignments = symbol.assignmentDeclarationMembers;
7019+
if (assignments) {
7020+
const decls = arrayFrom(assignments.values());
7021+
for (const member of decls) {
7022+
const assignmentKind = getAssignmentDeclarationKind(member as BinaryExpression | CallExpression);
7023+
const isInstanceMember = assignmentKind === AssignmentDeclarationKind.PrototypeProperty
7024+
|| assignmentKind === AssignmentDeclarationKind.ThisProperty
7025+
|| assignmentKind === AssignmentDeclarationKind.ObjectDefinePrototypeProperty
7026+
|| assignmentKind === AssignmentDeclarationKind.Prototype; // A straight `Prototype` assignment probably can never have a computed name
7027+
if (isStatic === !isInstanceMember && hasLateBindableName(member)) {
7028+
lateBindMember(symbol, earlySymbols, lateSymbols, member);
7029+
}
7030+
}
7031+
}
70037032

70047033
links[resolutionKind] = combineSymbolTables(earlySymbols, lateSymbols) || emptySymbols;
70057034
}

src/compiler/transformers/declarations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1137,7 +1137,7 @@ namespace ts {
11371137
fakespace.symbol = props[0].parent!;
11381138
const declarations = mapDefined(props, p => {
11391139
if (!isPropertyAccessExpression(p.valueDeclaration)) {
1140-
return undefined;
1140+
return undefined; // TODO: Handle element access expressions that created late bound names (rather than silently omitting them)
11411141
}
11421142
getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p.valueDeclaration);
11431143
const type = resolver.createTypeOfDeclaration(p.valueDeclaration, fakespace, declarationEmitNodeBuilderFlags, symbolTracker);

src/compiler/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -805,7 +805,7 @@ namespace ts {
805805

806806
export type PropertyName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName;
807807

808-
export type DeclarationName = Identifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern;
808+
export type DeclarationName = Identifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | ElementAccessExpression | BindingPattern;
809809

810810
export interface Declaration extends Node {
811811
_declarationBrand: any;
@@ -3746,7 +3746,7 @@ namespace ts {
37463746
Classifiable = Class | Enum | TypeAlias | Interface | TypeParameter | Module | Alias,
37473747

37483748
/* @internal */
3749-
LateBindingContainer = Class | Interface | TypeLiteral | ObjectLiteral,
3749+
LateBindingContainer = Class | Interface | TypeLiteral | ObjectLiteral | Function,
37503750
}
37513751

37523752
export interface Symbol {
@@ -3766,6 +3766,7 @@ namespace ts {
37663766
/* @internal */ isReferenced?: SymbolFlags; // True if the symbol is referenced elsewhere. Keeps track of the meaning of a reference in case a symbol is both a type parameter and parameter.
37673767
/* @internal */ isReplaceableByMethod?: boolean; // Can this Javascript class property be replaced by a method symbol?
37683768
/* @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments
3769+
/* @internal */ assignmentDeclarationMembers?: Map<Declaration>;
37693770
}
37703771

37713772
/* @internal */

0 commit comments

Comments
 (0)