Skip to content

Support declaration emit for late bound element accesses assigned to functions in both TS and JS #36593

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3057,9 +3057,20 @@ namespace ts {
function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined;
function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol {
if (moduleSymbol) {
const links = getSymbolLinks(moduleSymbol);
if (!dontResolveAlias) {
links.resolvedExternalModuleSymbol = "circular";
}
const exportEquals = resolveSymbol(moduleSymbol.exports!.get(InternalSymbolName.ExportEquals), dontResolveAlias);
const exported = getCommonJsExportEquals(getMergedSymbol(exportEquals), getMergedSymbol(moduleSymbol));
return getMergedSymbol(exported) || moduleSymbol;
const result = getMergedSymbol(exported) || moduleSymbol;
if (!dontResolveAlias) {
// we "cache" this result, but because both the input _and_ the output are mergeable, literally every call could need
// a different result, assuming the inputs and outputs get merged with. So instead we just use this as a flag that
// export assignment resolution has occured on this symbol at least once. (because it might happen many times!)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this is basically just circularity detection? why do you need an initial value "circular" then? I think I'm misunderstanding your comments.

typo: occurred

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I have a value to flag before the result is ready to handle the reentrant case.

links.resolvedExternalModuleSymbol = result;
}
return result;
}
return undefined!;
}
Expand Down Expand Up @@ -5531,7 +5542,7 @@ namespace ts {
function serializeSymbolWorker(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean) {
const symbolName = unescapeLeadingUnderscores(symbol.escapedName);
const isDefault = symbol.escapedName === InternalSymbolName.Default;
if (isStringANonContextualKeyword(symbolName) && !isDefault) {
if ((isStringANonContextualKeyword(symbolName) || !isIdentifierText(symbolName, languageVersion)) && !isDefault && symbol.escapedName !== InternalSymbolName.ExportEquals) {
// Oh no. We cannot use this symbol's name as it's name... It's likely some jsdoc had an invalid name like `export` or `default` :(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: as its name
not as it's name

context.encounteredError = true;
// TODO: Issue error via symbol tracker?
Expand Down Expand Up @@ -5682,7 +5693,8 @@ namespace ts {
}

function getNamespaceMembersForSerialization(symbol: Symbol) {
return !symbol.exports ? [] : filter(arrayFrom((symbol.exports).values()), p => !((p.flags & SymbolFlags.Prototype) || (p.escapedName === "prototype")));
const exports = getExportsOfSymbol(symbol);
return !exports ? [] : filter(arrayFrom((exports).values()), p => !((p.flags & SymbolFlags.Prototype) || (p.escapedName === "prototype")));
}

function isTypeOnlyNamespace(symbol: Symbol) {
Expand Down Expand Up @@ -8884,6 +8896,17 @@ namespace ts {
function getResolvedMembersOrExportsOfSymbol(symbol: Symbol, resolutionKind: MembersOrExportsResolutionKind): UnderscoreEscapedMap<Symbol> {
const links = getSymbolLinks(symbol);
if (!links[resolutionKind]) {
// We _must_ resolve any possible commonjs export assignments _before_ merging in late bound members, as cjs export assignments may cause
// things to be merged (?!?) into this symbol; moreover, we _can't_ resolve those export assignments if we're already resolving the containing
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you give an example test of this? I think I can imagine it, but I may be thinking of something different.

Copy link
Member Author

@weswigham weswigham Feb 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function Foo() {
}
const lateBound = "field";
Foo[lateBound] = 12;
Foo.A = function() {}
/**
 * @typedef {number | string} FooProp
 */
module.exports = Foo.A;

The export assignment is the A prop of Foo, which means we need the full list of Foo's exports to check if A is a member (and what it is), which means we need to do a cjs merge to merge the typedefs into the exports, which means we need to know the target of Foo.A, which means we need the A member of Foo, which means we need Foo's exports, which means....

Yeah, the process is inherently circular. This peek short circuits it, since symbol merging makes all the partial results layer together in the end anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which means we need to do a cjs merge to merge the typedefs into the exports

I’m missing how we get to here... did you mean to use FooProp in your example scenario? I can’t tell why resolving anything related to Foo would require resolving any exports of the top-level module.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m missing how we get to here... did you mean to use FooProp in your example scenario?

Nope. I did not.

I can’t tell why resolving anything related to Foo would require resolving any exports of the top-level module.

The "top level module" is defined by a export= member which is an alias pointing at Foo.A. (And a typedef). We don't really support having both an export= and another top-level export, so to handle that scenario, we merge the other top-level export into the target of the export= alias symbol. I suppose the more telling example is when the final line is module.exports = Foo; instead. Foo clearly needs to have both FooProp and "field" members merged into it. OK, so you handle that by layering the export table merges (and forcing the cjs exports merge to always come first) - once that's done, you look at the module.exports = Foo.A case. Well, in resolving Foo.A, you need the exports of Foo, which means getting its exports; before it's safe to get its exports, we don't yet know if it's the target of the export = (imagine if Foo.A = Foo!), so we must perform the containing file's cjs merge (to ensure the exports of the target are complete, in case the target winds up being the symbol we're about to look up). Doing so would attempt to resolve export= again, which is a circularity, as we are already doing that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we merge the other top-level export into the target of the export= alias symbol

Ohhh, yep, I get it now. Thanks 👍

// module (as then we'll issue a circularity error)
Comment on lines +8899 to +8901
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I haven’t approved this PR is because I don’t really understand this (or really, why changes in alias resolution were involved generally), so I was hoping someone else would. But since nobody else has come by, if you want to expound on this, I’ll take a closer look.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree; it seems like this is needed to support checking, not necessarily declaration emit, since it skips late-bound symbols anyway.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checking changed because the declaration emit made it evident it was wrong. We were using symbol.exports before; this was missing late bound members, so we swapped to using getExportsOfSymbol in this PR; however using that made it obvious that the resolved exports were being cached without any cjs merged exports in them! (So symbol.exports had members that were never reflected in the resolved exports!) That was because we were not reliably doing the cjs merge before caching the resolved exports; this new check guarantees that we try to do the cjs merge before caching resolved exports, every time.

const p = symbol.valueDeclaration?.parent;
if (p && isSourceFile(p) && p.symbol && !getSymbolLinks(p.symbol).resolvedExternalModuleSymbol) {
const exported = resolveExternalModuleSymbol(p.symbol);
const targetLinks = exported && getSymbolLinks(exported);
if (targetLinks && targetLinks[resolutionKind]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would targetLinks?.[resolutionKind] work or did that make it into the spec?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that works

return links[resolutionKind] = targetLinks[resolutionKind]!;
}
}
const isStatic = resolutionKind === MembersOrExportsResolutionKind.resolvedExports;
const earlySymbols = !isStatic ? symbol.members :
symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol) :
Expand Down Expand Up @@ -35082,8 +35105,8 @@ namespace ts {
}
}

function createTypeOfDeclaration(declarationIn: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean) {
const declaration = getParseTreeNode(declarationIn, isVariableLikeOrAccessor);
function createTypeOfDeclaration(declarationIn: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression | ElementAccessExpression | BinaryExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean) {
const declaration = getParseTreeNode(declarationIn, isPossiblyPropertyLikeDeclaration);
if (!declaration) {
return createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode;
}
Expand Down
13 changes: 10 additions & 3 deletions src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1157,13 +1157,20 @@ namespace ts {
fakespace.locals = createSymbolTable(props);
fakespace.symbol = props[0].parent!;
const declarations = mapDefined(props, p => {
if (!isPropertyAccessExpression(p.valueDeclaration)) {
return undefined; // TODO GH#33569: Handle element access expressions that created late bound names (rather than silently omitting them)
if (!isPropertyAccessExpression(p.valueDeclaration) && !isElementAccessExpression(p.valueDeclaration) && !isBinaryExpression(p.valueDeclaration)) {
return undefined;
}
if (hasDynamicName(p.valueDeclaration) && !resolver.isLateBound(getParseTreeNode(p.valueDeclaration) as Declaration)) {
return undefined;
}
const name = unescapeLeadingUnderscores(p.escapedName);
if (!isIdentifierText(name, ScriptTarget.ES3)) {
return undefined; // TODO: Rather than quietly eliding (as is current behavior), maybe we should issue errors?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issuing errors would essentially conflict with this PR, though, right? It seems like this PR's intent is to elide.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issuing errors would be a breaking change, since our past behavior with late bound stuff has always been to elide what we couldn't emit. Still, it means we're quietly omitting type information from the declaration file. Usually that'd warrant an error in any other scenario, imo.

}
getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p.valueDeclaration);
const type = resolver.createTypeOfDeclaration(p.valueDeclaration, fakespace, declarationEmitNodeBuilderFlags, symbolTracker);
getSymbolAccessibilityDiagnostic = oldDiag;
const varDecl = createVariableDeclaration(unescapeLeadingUnderscores(p.escapedName), type, /*initializer*/ undefined);
const varDecl = createVariableDeclaration(name, type, /*initializer*/ undefined);
return createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([varDecl]));
});
const namespaceDecl = createModuleDeclaration(/*decorators*/ undefined, ensureModifiers(input), input.name!, createModuleBlock(declarations), NodeFlags.Namespace);
Expand Down
12 changes: 8 additions & 4 deletions src/compiler/transformers/declarations/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ namespace ts {
| TypeAliasDeclaration
| ConstructorDeclaration
| IndexSignatureDeclaration
| PropertyAccessExpression;
| PropertyAccessExpression
| ElementAccessExpression
| BinaryExpression;

export function canProduceDiagnostics(node: Node): node is DeclarationDiagnosticProducing {
return isVariableDeclaration(node) ||
Expand All @@ -48,7 +50,9 @@ namespace ts {
isTypeAliasDeclaration(node) ||
isConstructorDeclaration(node) ||
isIndexSignatureDeclaration(node) ||
isPropertyAccessExpression(node);
isPropertyAccessExpression(node) ||
isElementAccessExpression(node) ||
isBinaryExpression(node);
}

export function createGetSymbolAccessibilityDiagnosticForNodeName(node: DeclarationDiagnosticProducing) {
Expand Down Expand Up @@ -125,7 +129,7 @@ namespace ts {
}

export function createGetSymbolAccessibilityDiagnosticForNode(node: DeclarationDiagnosticProducing): (symbolAccessibilityResult: SymbolAccessibilityResult) => SymbolAccessibilityDiagnostic | undefined {
if (isVariableDeclaration(node) || isPropertyDeclaration(node) || isPropertySignature(node) || isPropertyAccessExpression(node) || isBindingElement(node) || isConstructorDeclaration(node)) {
if (isVariableDeclaration(node) || isPropertyDeclaration(node) || isPropertySignature(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node) || isBinaryExpression(node) || isBindingElement(node) || isConstructorDeclaration(node)) {
return getVariableDeclarationTypeVisibilityError;
}
else if (isSetAccessor(node) || isGetAccessor(node)) {
Expand Down Expand Up @@ -166,7 +170,7 @@ namespace ts {
}
// This check is to ensure we don't report error on constructor parameter property as that error would be reported during parameter emit
// The only exception here is if the constructor was marked as private. we are not emitting the constructor parameters at all.
else if (node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.PropertySignature ||
else if (node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.ElementAccessExpression || node.kind === SyntaxKind.BinaryExpression || node.kind === SyntaxKind.PropertySignature ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to break these long lines

(node.kind === SyntaxKind.Parameter && hasModifier(node.parent, ModifierFlags.Private))) {
// TODO(jfreeman): Deal with computed properties in error reporting.
if (hasModifier(node, ModifierFlags.Static)) {
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3920,7 +3920,7 @@ namespace ts {
isOptionalUninitializedParameterProperty(node: ParameterDeclaration): boolean;
isExpandoFunctionDeclaration(node: FunctionDeclaration): boolean;
getPropertiesOfContainerFunction(node: Declaration): Symbol[];
createTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean): TypeNode | undefined;
createTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression | ElementAccessExpression | BinaryExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean): TypeNode | undefined;
createReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker): TypeNode | undefined;
createTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker): TypeNode | undefined;
createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker): Expression;
Expand Down Expand Up @@ -4094,6 +4094,7 @@ namespace ts {
deferralParent?: Type; // Source union/intersection of a deferred type
cjsExportMerged?: Symbol; // Version of the symbol with all non export= exports merged with the export= target
typeOnlyDeclaration?: TypeOnlyCompatibleAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs
resolvedExternalModuleSymbol?: Symbol | "circular"; // Cached result of `resolveExternalModuleSymbol`
}

/* @internal */
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,10 @@ namespace ts {
return isVariableLike(node) || isAccessor(node);
}

export function isPossiblyPropertyLikeDeclaration(node: Node): node is AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression | ElementAccessExpression {
return isVariableLikeOrAccessor(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node) || isBinaryExpression(node);
}

export function isVariableDeclarationInVariableStatement(node: VariableDeclaration) {
return node.parent.kind === SyntaxKind.VariableDeclarationList
&& node.parent.parent.kind === SyntaxKind.VariableStatement;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//// [lateBoundElementAccessAssignmentDeclarations.ts]
export function foo() {}
foo.bar = 12;
const _private = Symbol();
foo[_private] = "ok";
const strMem = "strMemName";
foo[strMem] = "ok";
const dashStrMem = "dashed-str-mem";
foo[dashStrMem] = "ok";
const numMem = 42;
foo[numMem] = "ok";

const x: string = foo[_private];
const y: string = foo[strMem];
const z: string = foo[numMem];
const a: string = foo[dashStrMem];

//// [lateBoundElementAccessAssignmentDeclarations.js]
export function foo() { }
foo.bar = 12;
const _private = Symbol();
foo[_private] = "ok";
const strMem = "strMemName";
foo[strMem] = "ok";
const dashStrMem = "dashed-str-mem";
foo[dashStrMem] = "ok";
const numMem = 42;
foo[numMem] = "ok";
const x = foo[_private];
const y = foo[strMem];
const z = foo[numMem];
const a = foo[dashStrMem];


//// [lateBoundElementAccessAssignmentDeclarations.d.ts]
export declare function foo(): void;
export declare namespace foo {
var bar: number;
var strMemName: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
=== tests/cases/compiler/lateBoundElementAccessAssignmentDeclarations.ts ===
export function foo() {}
>foo : Symbol(foo, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 0), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 24), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 2, 26), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 4, 28), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 6, 36) ... and 1 more)

foo.bar = 12;
>foo.bar : Symbol(foo.bar, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 24))
>foo : Symbol(foo, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 0), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 24), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 2, 26), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 4, 28), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 6, 36) ... and 1 more)
>bar : Symbol(foo.bar, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 24))

const _private = Symbol();
>_private : Symbol(_private, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 2, 5))
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))

foo[_private] = "ok";
>foo : Symbol(foo, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 0), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 24), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 2, 26), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 4, 28), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 6, 36) ... and 1 more)
>_private : Symbol(_private, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 2, 5))

const strMem = "strMemName";
>strMem : Symbol(strMem, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 4, 5))

foo[strMem] = "ok";
>foo : Symbol(foo, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 0), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 24), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 2, 26), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 4, 28), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 6, 36) ... and 1 more)
>strMem : Symbol(strMem, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 4, 5))

const dashStrMem = "dashed-str-mem";
>dashStrMem : Symbol(dashStrMem, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 6, 5))

foo[dashStrMem] = "ok";
>foo : Symbol(foo, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 0), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 24), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 2, 26), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 4, 28), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 6, 36) ... and 1 more)
>dashStrMem : Symbol(dashStrMem, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 6, 5))

const numMem = 42;
>numMem : Symbol(numMem, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 8, 5))

foo[numMem] = "ok";
>foo : Symbol(foo, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 0), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 24), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 2, 26), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 4, 28), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 6, 36) ... and 1 more)
>numMem : Symbol(numMem, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 8, 5))

const x: string = foo[_private];
>x : Symbol(x, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 11, 5))
>foo : Symbol(foo, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 0), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 24), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 2, 26), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 4, 28), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 6, 36) ... and 1 more)
>_private : Symbol(_private, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 2, 5))

const y: string = foo[strMem];
>y : Symbol(y, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 12, 5))
>foo : Symbol(foo, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 0), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 24), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 2, 26), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 4, 28), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 6, 36) ... and 1 more)
>strMem : Symbol(strMem, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 4, 5))

const z: string = foo[numMem];
>z : Symbol(z, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 13, 5))
>foo : Symbol(foo, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 0), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 24), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 2, 26), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 4, 28), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 6, 36) ... and 1 more)
>numMem : Symbol(numMem, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 8, 5))

const a: string = foo[dashStrMem];
>a : Symbol(a, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 14, 5))
>foo : Symbol(foo, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 0), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 0, 24), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 2, 26), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 4, 28), Decl(lateBoundElementAccessAssignmentDeclarations.ts, 6, 36) ... and 1 more)
>dashStrMem : Symbol(dashStrMem, Decl(lateBoundElementAccessAssignmentDeclarations.ts, 6, 5))

Loading