Skip to content

Fix declaration emit for property references of imported object literal types #39055

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
merged 2 commits into from
Jun 13, 2020
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
32 changes: 18 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
60 changes: 60 additions & 0 deletions tests/baselines/reference/uniqueSymbolPropertyDeclarationEmit.js
Original file line number Diff line number Diff line change
@@ -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]: {};
};
26 changes: 26 additions & 0 deletions tests/cases/compiler/uniqueSymbolPropertyDeclarationEmit.ts
Original file line number Diff line number Diff line change
@@ -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;
};