From d578f65cb5a09a4795559d531c209e9699df9849 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 12 Jun 2020 14:26:42 -0700 Subject: [PATCH 1/2] Fix declaration emit for property references of imported object literal types --- src/compiler/checker.ts | 32 ++++++------ .../uniqueSymbolPropertyDeclarationEmit.js | 49 +++++++++++++++++++ .../uniqueSymbolPropertyDeclarationEmit.ts | 19 +++++++ 3 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 tests/baselines/reference/uniqueSymbolPropertyDeclarationEmit.js create mode 100644 tests/cases/compiler/uniqueSymbolPropertyDeclarationEmit.ts 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..c50f7cdef1559 --- /dev/null +++ b/tests/baselines/reference/uniqueSymbolPropertyDeclarationEmit.js @@ -0,0 +1,49 @@ +//// [tests/cases/compiler/uniqueSymbolPropertyDeclarationEmit.ts] //// + +//// [test.ts] +import Op from './op'; + +export default function foo() { + return { + [Op.or]: [], + }; +} + +//// [op.ts] +declare const Op: { + readonly or: unique symbol; +}; + +export default Op; + + +//// [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")); +function foo() { + var _a; + return _a = {}, + _a[op_1["default"].or] = [], + _a; +} +exports["default"] = foo; + + +//// [op.d.ts] +declare const Op: { + readonly or: unique symbol; +}; +export default Op; +//// [test.d.ts] +import Op from './op'; +export default function foo(): { + [Op.or]: any[]; +}; diff --git a/tests/cases/compiler/uniqueSymbolPropertyDeclarationEmit.ts b/tests/cases/compiler/uniqueSymbolPropertyDeclarationEmit.ts new file mode 100644 index 0000000000000..6e072590ce602 --- /dev/null +++ b/tests/cases/compiler/uniqueSymbolPropertyDeclarationEmit.ts @@ -0,0 +1,19 @@ +// @noTypesAndSymbols: true +// @esModuleInterop: true +// @declaration: true + +// @Filename: test.ts +import Op from './op'; + +export default function foo() { + return { + [Op.or]: [], + }; +} + +// @Filename: op.ts +declare const Op: { + readonly or: unique symbol; +}; + +export default Op; From f0dc1ce24a47f4c8a037297df8ed83617a2bbf70 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 12 Jun 2020 15:42:16 -0700 Subject: [PATCH 2/2] Add declaration file to test --- .../reference/uniqueSymbolPropertyDeclarationEmit.js | 11 +++++++++++ .../compiler/uniqueSymbolPropertyDeclarationEmit.ts | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/tests/baselines/reference/uniqueSymbolPropertyDeclarationEmit.js b/tests/baselines/reference/uniqueSymbolPropertyDeclarationEmit.js index c50f7cdef1559..e9dfa71ca56d8 100644 --- a/tests/baselines/reference/uniqueSymbolPropertyDeclarationEmit.js +++ b/tests/baselines/reference/uniqueSymbolPropertyDeclarationEmit.js @@ -2,10 +2,12 @@ //// [test.ts] import Op from './op'; +import { Po } from './po'; export default function foo() { return { [Op.or]: [], + [Po.ro]: {} }; } @@ -16,6 +18,11 @@ declare const Op: { export default Op; +//// [po.d.ts] +export declare const Po: { + readonly ro: unique symbol; +}; + //// [op.js] "use strict"; @@ -28,10 +35,12 @@ var __importDefault = (this && this.__importDefault) || function (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; @@ -44,6 +53,8 @@ declare const Op: { 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 index 6e072590ce602..2456cda2edfd5 100644 --- a/tests/cases/compiler/uniqueSymbolPropertyDeclarationEmit.ts +++ b/tests/cases/compiler/uniqueSymbolPropertyDeclarationEmit.ts @@ -4,10 +4,12 @@ // @Filename: test.ts import Op from './op'; +import { Po } from './po'; export default function foo() { return { [Op.or]: [], + [Po.ro]: {} }; } @@ -17,3 +19,8 @@ declare const Op: { }; export default Op; + +// @Filename: po.d.ts +export declare const Po: { + readonly ro: unique symbol; +};