Skip to content

Commit 2938168

Browse files
authored
Support some late-bound special property assignments (microsoft#33220)
* Support some late-bound special property assignments * Integrate PR feedback * PR feedback * Enable declaration on core tests * Specialize type of binary expression used for late binding, speculative fix to navigation bar, merge check and type for elem/property accesses * Add test showing current nav bar behavior (specifically the lack thereof)
1 parent 304fcee commit 2938168

File tree

51 files changed

+1897
-47
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1897
-47
lines changed

src/compiler/binder.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2622,7 +2622,12 @@ namespace ts {
26222622
// Declare a 'member' if the container is an ES5 class or ES6 constructor
26232623
constructorSymbol.members = constructorSymbol.members || createSymbolTable();
26242624
// It's acceptable for multiple 'this' assignments of the same identifier to occur
2625-
declareSymbol(constructorSymbol.members, constructorSymbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property);
2625+
if (hasDynamicName(node)) {
2626+
bindDynamicallyNamedThisPropertyAssignment(node, constructorSymbol);
2627+
}
2628+
else {
2629+
declareSymbol(constructorSymbol.members, constructorSymbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property);
2630+
}
26262631
addDeclarationToSymbol(constructorSymbol, constructorSymbol.valueDeclaration, SymbolFlags.Class);
26272632
}
26282633
break;
@@ -2636,7 +2641,12 @@ namespace ts {
26362641
// Bind this property to the containing class
26372642
const containingClass = thisContainer.parent;
26382643
const symbolTable = hasModifier(thisContainer, ModifierFlags.Static) ? containingClass.symbol.exports! : containingClass.symbol.members!;
2639-
declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.None, /*isReplaceableByMethod*/ true);
2644+
if (hasDynamicName(node)) {
2645+
bindDynamicallyNamedThisPropertyAssignment(node, containingClass.symbol);
2646+
}
2647+
else {
2648+
declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.None, /*isReplaceableByMethod*/ true);
2649+
}
26402650
break;
26412651
case SyntaxKind.SourceFile:
26422652
// this.property = assignment in a source file -- declare symbol in exports for a module, in locals for a script
@@ -2653,6 +2663,18 @@ namespace ts {
26532663
}
26542664
}
26552665

2666+
function bindDynamicallyNamedThisPropertyAssignment(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol) {
2667+
bindAnonymousDeclaration(node, SymbolFlags.Property, InternalSymbolName.Computed);
2668+
addLateBoundAssignmentDeclarationToSymbol(node, symbol);
2669+
}
2670+
2671+
function addLateBoundAssignmentDeclarationToSymbol(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol | undefined) {
2672+
if (symbol) {
2673+
const members = symbol.assignmentDeclarationMembers || (symbol.assignmentDeclarationMembers = createMap());
2674+
members.set("" + getNodeId(node), node);
2675+
}
2676+
}
2677+
26562678
function bindSpecialPropertyDeclaration(node: PropertyAccessExpression) {
26572679
if (node.expression.kind === SyntaxKind.ThisKeyword) {
26582680
bindThisPropertyAssignment(node);
@@ -2722,7 +2744,14 @@ namespace ts {
27222744
bindExportsPropertyAssignment(node);
27232745
}
27242746
else {
2725-
bindStaticPropertyAssignment(lhs);
2747+
if (hasDynamicName(node)) {
2748+
bindAnonymousDeclaration(node, SymbolFlags.Property | SymbolFlags.Assignment, InternalSymbolName.Computed);
2749+
const sym = bindPotentiallyMissingNamespaces(parentSymbol, lhs.expression, isTopLevelNamespaceAssignment(lhs), /*isPrototype*/ false, /*containerIsClass*/ false);
2750+
addLateBoundAssignmentDeclarationToSymbol(node, sym);
2751+
}
2752+
else {
2753+
bindStaticPropertyAssignment(lhs);
2754+
}
27262755
}
27272756
}
27282757

src/compiler/checker.ts

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2860,7 +2860,7 @@ namespace ts {
28602860
}
28612861

28622862
function getExportsOfSymbol(symbol: Symbol): SymbolTable {
2863-
return symbol.flags & SymbolFlags.Class ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedExports) :
2863+
return symbol.flags & SymbolFlags.LateBindingContainer ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedExports) :
28642864
symbol.flags & SymbolFlags.Module ? getExportsOfModule(symbol) :
28652865
symbol.exports || emptySymbols;
28662866
}
@@ -4224,7 +4224,15 @@ namespace ts {
42244224
if (context.tracker.trackSymbol && getCheckFlags(propertySymbol) & CheckFlags.Late) {
42254225
const decl = first(propertySymbol.declarations);
42264226
if (hasLateBindableName(decl)) {
4227-
trackComputedName(decl.name, saveEnclosingDeclaration, context);
4227+
if (isBinaryExpression(decl)) {
4228+
const name = getNameOfDeclaration(decl);
4229+
if (name && isElementAccessExpression(name) && isPropertyAccessEntityNameExpression(name.argumentExpression)) {
4230+
trackComputedName(name.argumentExpression, saveEnclosingDeclaration, context);
4231+
}
4232+
}
4233+
else {
4234+
trackComputedName(decl.name.expression, saveEnclosingDeclaration, context);
4235+
}
42284236
}
42294237
}
42304238
const propertyName = symbolToName(propertySymbol, context, SymbolFlags.Value, /*expectsIdentifier*/ true);
@@ -4440,7 +4448,7 @@ namespace ts {
44404448
return <BindingName>elideInitializerAndSetEmitFlags(node);
44414449
function elideInitializerAndSetEmitFlags(node: Node): Node {
44424450
if (context.tracker.trackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) {
4443-
trackComputedName(node, context.enclosingDeclaration, context);
4451+
trackComputedName(node.expression, context.enclosingDeclaration, context);
44444452
}
44454453
const visited = visitEachChild(node, elideInitializerAndSetEmitFlags, nullTransformationContext, /*nodesVisitor*/ undefined, elideInitializerAndSetEmitFlags)!;
44464454
const clone = nodeIsSynthesized(visited) ? visited : getSynthesizedClone(visited);
@@ -4452,10 +4460,10 @@ namespace ts {
44524460
}
44534461
}
44544462

4455-
function trackComputedName(node: LateBoundName, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) {
4463+
function trackComputedName(accessExpression: EntityNameOrEntityNameExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) {
44564464
if (!context.tracker.trackSymbol) return;
44574465
// get symbol of the first identifier of the entityName
4458-
const firstIdentifier = getFirstIdentifier(node.expression);
4466+
const firstIdentifier = getFirstIdentifier(accessExpression);
44594467
const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true);
44604468
if (name) {
44614469
context.tracker.trackSymbol(name, enclosingDeclaration, SymbolFlags.Value);
@@ -7182,8 +7190,10 @@ namespace ts {
71827190
if (declaration.kind === SyntaxKind.ExportAssignment) {
71837191
type = widenTypeForVariableLikeDeclaration(checkExpressionCached((<ExportAssignment>declaration).expression), declaration);
71847192
}
7185-
else if (isInJSFile(declaration) &&
7186-
(isCallExpression(declaration) || isBinaryExpression(declaration) || isPropertyAccessExpression(declaration) && isBinaryExpression(declaration.parent))) {
7193+
else if (
7194+
isBinaryExpression(declaration) ||
7195+
(isInJSFile(declaration) &&
7196+
(isCallExpression(declaration) || isPropertyAccessExpression(declaration) && isBinaryExpression(declaration.parent)))) {
71877197
type = getWidenedTypeForAssignmentDeclaration(symbol);
71887198
}
71897199
else if (isJSDocPropertyLikeTag(declaration)
@@ -8201,9 +8211,12 @@ namespace ts {
82018211
* - The type of its expression is a string or numeric literal type, or is a `unique symbol` type.
82028212
*/
82038213
function isLateBindableName(node: DeclarationName): node is LateBoundName {
8204-
return isComputedPropertyName(node)
8205-
&& isEntityNameExpression(node.expression)
8206-
&& isTypeUsableAsPropertyName(checkComputedPropertyName(node));
8214+
if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) {
8215+
return false;
8216+
}
8217+
const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression;
8218+
return isEntityNameExpression(expr)
8219+
&& isTypeUsableAsPropertyName(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached(expr));
82078220
}
82088221

82098222
function isLateBoundName(name: __String): boolean {
@@ -8215,7 +8228,7 @@ namespace ts {
82158228
/**
82168229
* Indicates whether a declaration has a late-bindable dynamic name.
82178230
*/
8218-
function hasLateBindableName(node: Declaration): node is LateBoundDeclaration {
8231+
function hasLateBindableName(node: Declaration): node is LateBoundDeclaration | LateBoundBinaryExpressionDeclaration {
82198232
const name = getNameOfDeclaration(node);
82208233
return !!name && isLateBindableName(name);
82218234
}
@@ -8252,7 +8265,7 @@ namespace ts {
82528265
* late-bound members that `addDeclarationToSymbol` in binder.ts performs for early-bound
82538266
* members.
82548267
*/
8255-
function addDeclarationToLateBoundSymbol(symbol: Symbol, member: LateBoundDeclaration, symbolFlags: SymbolFlags) {
8268+
function addDeclarationToLateBoundSymbol(symbol: Symbol, member: LateBoundDeclaration | BinaryExpression, symbolFlags: SymbolFlags) {
82568269
Debug.assert(!!(getCheckFlags(symbol) & CheckFlags.Late), "Expected a late-bound symbol.");
82578270
symbol.flags |= symbolFlags;
82588271
getSymbolLinks(member.symbol).lateSymbol = symbol;
@@ -8297,14 +8310,15 @@ namespace ts {
82978310
* @param lateSymbols The late-bound symbols of the parent.
82988311
* @param decl The member to bind.
82998312
*/
8300-
function lateBindMember(parent: Symbol, earlySymbols: SymbolTable | undefined, lateSymbols: SymbolTable, decl: LateBoundDeclaration) {
8313+
function lateBindMember(parent: Symbol, earlySymbols: SymbolTable | undefined, lateSymbols: SymbolTable, decl: LateBoundDeclaration | LateBoundBinaryExpressionDeclaration) {
83018314
Debug.assert(!!decl.symbol, "The member is expected to have a symbol.");
83028315
const links = getNodeLinks(decl);
83038316
if (!links.resolvedSymbol) {
83048317
// In the event we attempt to resolve the late-bound name of this member recursively,
83058318
// fall back to the early-bound name of this member.
83068319
links.resolvedSymbol = decl.symbol;
8307-
const type = checkComputedPropertyName(decl.name);
8320+
const declName = isBinaryExpression(decl) ? decl.left : decl.name;
8321+
const type = isElementAccessExpression(declName) ? checkExpressionCached(declName.argumentExpression) : checkComputedPropertyName(declName);
83088322
if (isTypeUsableAsPropertyName(type)) {
83098323
const memberName = getPropertyNameFromType(type);
83108324
const symbolFlags = decl.symbol.flags;
@@ -8321,9 +8335,9 @@ namespace ts {
83218335
// If we have an existing early-bound member, combine its declarations so that we can
83228336
// report an error at each declaration.
83238337
const declarations = earlySymbol ? concatenate(earlySymbol.declarations, lateSymbol.declarations) : lateSymbol.declarations;
8324-
const name = !(type.flags & TypeFlags.UniqueESSymbol) && unescapeLeadingUnderscores(memberName) || declarationNameToString(decl.name);
8338+
const name = !(type.flags & TypeFlags.UniqueESSymbol) && unescapeLeadingUnderscores(memberName) || declarationNameToString(declName);
83258339
forEach(declarations, declaration => error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Property_0_was_also_declared_here, name));
8326-
error(decl.name || decl, Diagnostics.Duplicate_property_0, name);
8340+
error(declName || decl, Diagnostics.Duplicate_property_0, name);
83278341
lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late);
83288342
}
83298343
lateSymbol.nameType = type;
@@ -8365,6 +8379,20 @@ namespace ts {
83658379
}
83668380
}
83678381
}
8382+
const assignments = symbol.assignmentDeclarationMembers;
8383+
if (assignments) {
8384+
const decls = arrayFrom(assignments.values());
8385+
for (const member of decls) {
8386+
const assignmentKind = getAssignmentDeclarationKind(member as BinaryExpression | CallExpression);
8387+
const isInstanceMember = assignmentKind === AssignmentDeclarationKind.PrototypeProperty
8388+
|| assignmentKind === AssignmentDeclarationKind.ThisProperty
8389+
|| assignmentKind === AssignmentDeclarationKind.ObjectDefinePrototypeProperty
8390+
|| assignmentKind === AssignmentDeclarationKind.Prototype; // A straight `Prototype` assignment probably can never have a computed name
8391+
if (isStatic === !isInstanceMember && hasLateBindableName(member)) {
8392+
lateBindMember(symbol, earlySymbols, lateSymbols, member);
8393+
}
8394+
}
8395+
}
83688396

83698397
links[resolutionKind] = combineSymbolTables(earlySymbols, lateSymbols) || emptySymbols;
83708398
}

src/compiler/transformers/declarations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1130,7 +1130,7 @@ namespace ts {
11301130
fakespace.symbol = props[0].parent!;
11311131
const declarations = mapDefined(props, p => {
11321132
if (!isPropertyAccessExpression(p.valueDeclaration)) {
1133-
return undefined;
1133+
return undefined; // TODO GH#33569: Handle element access expressions that created late bound names (rather than silently omitting them)
11341134
}
11351135
getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p.valueDeclaration);
11361136
const type = resolver.createTypeOfDeclaration(p.valueDeclaration, fakespace, declarationEmitNodeBuilderFlags, symbolTracker);

src/compiler/types.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,7 @@ namespace ts {
814814

815815
export type PropertyName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName;
816816

817-
export type DeclarationName = Identifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern;
817+
export type DeclarationName = Identifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | ElementAccessExpression | BindingPattern;
818818

819819
export interface Declaration extends Node {
820820
_declarationBrand: any;
@@ -829,12 +829,27 @@ namespace ts {
829829
name: ComputedPropertyName;
830830
}
831831

832+
/* @internal */
833+
export interface DynamicNamedBinaryExpression extends BinaryExpression {
834+
left: ElementAccessExpression;
835+
}
836+
832837
/* @internal */
833838
// A declaration that supports late-binding (used in checker)
834839
export interface LateBoundDeclaration extends DynamicNamedDeclaration {
835840
name: LateBoundName;
836841
}
837842

843+
/* @internal */
844+
export interface LateBoundBinaryExpressionDeclaration extends DynamicNamedBinaryExpression {
845+
left: LateBoundElementAccessExpression;
846+
}
847+
848+
/* @internal */
849+
export interface LateBoundElementAccessExpression extends ElementAccessExpression {
850+
argumentExpression: EntityNameExpression;
851+
}
852+
838853
export interface DeclarationStatement extends NamedDeclaration, Statement {
839854
name?: Identifier | StringLiteral | NumericLiteral;
840855
}
@@ -3828,7 +3843,7 @@ namespace ts {
38283843
Classifiable = Class | Enum | TypeAlias | Interface | TypeParameter | Module | Alias,
38293844

38303845
/* @internal */
3831-
LateBindingContainer = Class | Interface | TypeLiteral | ObjectLiteral,
3846+
LateBindingContainer = Class | Interface | TypeLiteral | ObjectLiteral | Function,
38323847
}
38333848

38343849
export interface Symbol {
@@ -3848,6 +3863,7 @@ namespace ts {
38483863
/* @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.
38493864
/* @internal */ isReplaceableByMethod?: boolean; // Can this Javascript class property be replaced by a method symbol?
38503865
/* @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments
3866+
/* @internal */ assignmentDeclarationMembers?: Map<Declaration>; // detected late-bound assignment declarations associated with the symbol
38513867
}
38523868

38533869
/* @internal */

0 commit comments

Comments
 (0)