diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 12fbed724ec1f..5c6b8ea05064d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3488,16 +3488,17 @@ namespace ts { * Attempts to find the symbol corresponding to the container a symbol is in - usually this * is just its' `.parent`, but for locals, this value is `undefined` */ - function getContainersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined): Symbol[] | undefined { + function getContainersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags): Symbol[] | undefined { const container = getParentOfSymbol(symbol); // Type parameters end up in the `members` lists but are not externally visible if (container && !(symbol.flags & SymbolFlags.TypeParameter)) { const additionalContainers = mapDefined(container.declarations, fileSymbolIfFileSymbolExportEqualsContainer); const reexportContainers = enclosingDeclaration && getAlternativeContainingModules(symbol, enclosingDeclaration); + const objectLiteralContainer = getVariableDeclarationOfObjectLiteral(container, meaning); if (enclosingDeclaration && getAccessibleSymbolChain(container, enclosingDeclaration, SymbolFlags.Namespace, /*externalOnly*/ false)) { - return concatenate(concatenate([container], additionalContainers), reexportContainers); // This order expresses a preference for the real container if it is in scope + return append(concatenate(concatenate([container], additionalContainers), reexportContainers), objectLiteralContainer); // This order expresses a preference for the real container if it is in scope } - const res = append(additionalContainers, container); + const res = append(append(additionalContainers, container), objectLiteralContainer); return concatenate(res, reexportContainers); } const candidates = mapDefined(symbol.declarations, d => { @@ -3522,6 +3523,18 @@ namespace ts { } } + function getVariableDeclarationOfObjectLiteral(symbol: Symbol, meaning: SymbolFlags) { + // If we're trying to reference some object literal in, eg `var a = { x: 1 }`, the symbol for the literal, `__object`, is distinct + // from the symbol of the declaration it is being assigned to. Since we can use the declaration to refer to the literal, however, + // we'd like to make that connection here - potentially causing us to paint the declaration's visibility, and therefore the literal. + const firstDecl: Node | false = !!length(symbol.declarations) && first(symbol.declarations); + if (meaning & SymbolFlags.Value && firstDecl && firstDecl.parent && isVariableDeclaration(firstDecl.parent)) { + if (isObjectLiteralExpression(firstDecl) && firstDecl === firstDecl.parent.initializer || isTypeLiteralNode(firstDecl) && firstDecl === firstDecl.parent.type) { + return getSymbolOfNode(firstDecl.parent); + } + } + } + function getFileSymbolIfFileSymbolExportEqualsContainer(d: Declaration, container: Symbol) { const fileSymbol = getExternalModuleContainer(d); const exported = fileSymbol && fileSymbol.exports && fileSymbol.exports.get(InternalSymbolName.ExportEquals); @@ -3915,16 +3928,7 @@ namespace ts { // But it can't, hence the accessible is going to be undefined, but that doesn't mean m.c is inaccessible // It is accessible if the parent m is accessible because then m.c can be accessed through qualification - let containers = getContainersOfSymbol(symbol, enclosingDeclaration); - // If we're trying to reference some object literal in, eg `var a = { x: 1 }`, the symbol for the literal, `__object`, is distinct - // from the symbol of the declaration it is being assigned to. Since we can use the declaration to refer to the literal, however, - // we'd like to make that connection here - potentially causing us to paint the declaration's visibility, and therefore the literal. - const firstDecl: Node | false = !!length(symbol.declarations) && first(symbol.declarations); - if (!length(containers) && meaning & SymbolFlags.Value && firstDecl && isObjectLiteralExpression(firstDecl)) { - if (firstDecl.parent && isVariableDeclaration(firstDecl.parent) && firstDecl === firstDecl.parent.initializer) { - containers = [getSymbolOfNode(firstDecl.parent)]; - } - } + const containers = getContainersOfSymbol(symbol, enclosingDeclaration, meaning); const parentResult = isAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, initialSymbol === symbol ? getQualifiedLeftMeaning(meaning) : meaning, shouldComputeAliasesToMakeVisible, allowModules); if (parentResult) { return parentResult; @@ -5071,7 +5075,7 @@ namespace ts { needsQualification(accessibleSymbolChain[0], context.enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { // Go up and add our parent. - const parents = getContainersOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol, context.enclosingDeclaration); + const parents = getContainersOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol, context.enclosingDeclaration, meaning); if (length(parents)) { parentSpecifiers = parents!.map(symbol => some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol) diff --git a/tests/baselines/reference/uniqueSymbolPropertyDeclarationEmit.js b/tests/baselines/reference/uniqueSymbolPropertyDeclarationEmit.js new file mode 100644 index 0000000000000..e9dfa71ca56d8 --- /dev/null +++ b/tests/baselines/reference/uniqueSymbolPropertyDeclarationEmit.js @@ -0,0 +1,60 @@ +//// [tests/cases/compiler/uniqueSymbolPropertyDeclarationEmit.ts] //// + +//// [test.ts] +import Op from './op'; +import { Po } from './po'; + +export default function foo() { + return { + [Op.or]: [], + [Po.ro]: {} + }; +} + +//// [op.ts] +declare const Op: { + readonly or: unique symbol; +}; + +export default Op; + +//// [po.d.ts] +export declare const Po: { + readonly ro: unique symbol; +}; + + +//// [op.js] +"use strict"; +exports.__esModule = true; +exports["default"] = Op; +//// [test.js] +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +exports.__esModule = true; +var op_1 = __importDefault(require("./op")); +var po_1 = require("./po"); +function foo() { + var _a; + return _a = {}, + _a[op_1["default"].or] = [], + _a[po_1.Po.ro] = {}, + _a; +} +exports["default"] = foo; + + +//// [op.d.ts] +declare const Op: { + readonly or: unique symbol; +}; +export default Op; +//// [test.d.ts] +import Op from './op'; +import { Po } from './po'; +export default function foo(): { + [Op.or]: any[]; + [Po.ro]: {}; +}; diff --git a/tests/cases/compiler/uniqueSymbolPropertyDeclarationEmit.ts b/tests/cases/compiler/uniqueSymbolPropertyDeclarationEmit.ts new file mode 100644 index 0000000000000..2456cda2edfd5 --- /dev/null +++ b/tests/cases/compiler/uniqueSymbolPropertyDeclarationEmit.ts @@ -0,0 +1,26 @@ +// @noTypesAndSymbols: true +// @esModuleInterop: true +// @declaration: true + +// @Filename: test.ts +import Op from './op'; +import { Po } from './po'; + +export default function foo() { + return { + [Op.or]: [], + [Po.ro]: {} + }; +} + +// @Filename: op.ts +declare const Op: { + readonly or: unique symbol; +}; + +export default Op; + +// @Filename: po.d.ts +export declare const Po: { + readonly ro: unique symbol; +};