Skip to content

Commit d578f65

Browse files
committed
Fix declaration emit for property references of imported object literal types
1 parent f628bf8 commit d578f65

File tree

3 files changed

+86
-14
lines changed

3 files changed

+86
-14
lines changed

src/compiler/checker.ts

+18-14
Original file line numberDiff line numberDiff line change
@@ -3488,16 +3488,17 @@ namespace ts {
34883488
* Attempts to find the symbol corresponding to the container a symbol is in - usually this
34893489
* is just its' `.parent`, but for locals, this value is `undefined`
34903490
*/
3491-
function getContainersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined): Symbol[] | undefined {
3491+
function getContainersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags): Symbol[] | undefined {
34923492
const container = getParentOfSymbol(symbol);
34933493
// Type parameters end up in the `members` lists but are not externally visible
34943494
if (container && !(symbol.flags & SymbolFlags.TypeParameter)) {
34953495
const additionalContainers = mapDefined(container.declarations, fileSymbolIfFileSymbolExportEqualsContainer);
34963496
const reexportContainers = enclosingDeclaration && getAlternativeContainingModules(symbol, enclosingDeclaration);
3497+
const objectLiteralContainer = getVariableDeclarationOfObjectLiteral(container, meaning);
34973498
if (enclosingDeclaration && getAccessibleSymbolChain(container, enclosingDeclaration, SymbolFlags.Namespace, /*externalOnly*/ false)) {
3498-
return concatenate(concatenate([container], additionalContainers), reexportContainers); // This order expresses a preference for the real container if it is in scope
3499+
return append(concatenate(concatenate([container], additionalContainers), reexportContainers), objectLiteralContainer); // This order expresses a preference for the real container if it is in scope
34993500
}
3500-
const res = append(additionalContainers, container);
3501+
const res = append(append(additionalContainers, container), objectLiteralContainer);
35013502
return concatenate(res, reexportContainers);
35023503
}
35033504
const candidates = mapDefined(symbol.declarations, d => {
@@ -3522,6 +3523,18 @@ namespace ts {
35223523
}
35233524
}
35243525

3526+
function getVariableDeclarationOfObjectLiteral(symbol: Symbol, meaning: SymbolFlags) {
3527+
// If we're trying to reference some object literal in, eg `var a = { x: 1 }`, the symbol for the literal, `__object`, is distinct
3528+
// from the symbol of the declaration it is being assigned to. Since we can use the declaration to refer to the literal, however,
3529+
// we'd like to make that connection here - potentially causing us to paint the declaration's visibility, and therefore the literal.
3530+
const firstDecl: Node | false = !!length(symbol.declarations) && first(symbol.declarations);
3531+
if (meaning & SymbolFlags.Value && firstDecl && firstDecl.parent && isVariableDeclaration(firstDecl.parent)) {
3532+
if (isObjectLiteralExpression(firstDecl) && firstDecl === firstDecl.parent.initializer || isTypeLiteralNode(firstDecl) && firstDecl === firstDecl.parent.type) {
3533+
return getSymbolOfNode(firstDecl.parent);
3534+
}
3535+
}
3536+
}
3537+
35253538
function getFileSymbolIfFileSymbolExportEqualsContainer(d: Declaration, container: Symbol) {
35263539
const fileSymbol = getExternalModuleContainer(d);
35273540
const exported = fileSymbol && fileSymbol.exports && fileSymbol.exports.get(InternalSymbolName.ExportEquals);
@@ -3915,16 +3928,7 @@ namespace ts {
39153928
// But it can't, hence the accessible is going to be undefined, but that doesn't mean m.c is inaccessible
39163929
// It is accessible if the parent m is accessible because then m.c can be accessed through qualification
39173930

3918-
let containers = getContainersOfSymbol(symbol, enclosingDeclaration);
3919-
// If we're trying to reference some object literal in, eg `var a = { x: 1 }`, the symbol for the literal, `__object`, is distinct
3920-
// from the symbol of the declaration it is being assigned to. Since we can use the declaration to refer to the literal, however,
3921-
// we'd like to make that connection here - potentially causing us to paint the declaration's visibility, and therefore the literal.
3922-
const firstDecl: Node | false = !!length(symbol.declarations) && first(symbol.declarations);
3923-
if (!length(containers) && meaning & SymbolFlags.Value && firstDecl && isObjectLiteralExpression(firstDecl)) {
3924-
if (firstDecl.parent && isVariableDeclaration(firstDecl.parent) && firstDecl === firstDecl.parent.initializer) {
3925-
containers = [getSymbolOfNode(firstDecl.parent)];
3926-
}
3927-
}
3931+
const containers = getContainersOfSymbol(symbol, enclosingDeclaration, meaning);
39283932
const parentResult = isAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, initialSymbol === symbol ? getQualifiedLeftMeaning(meaning) : meaning, shouldComputeAliasesToMakeVisible, allowModules);
39293933
if (parentResult) {
39303934
return parentResult;
@@ -5071,7 +5075,7 @@ namespace ts {
50715075
needsQualification(accessibleSymbolChain[0], context.enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) {
50725076

50735077
// Go up and add our parent.
5074-
const parents = getContainersOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol, context.enclosingDeclaration);
5078+
const parents = getContainersOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol, context.enclosingDeclaration, meaning);
50755079
if (length(parents)) {
50765080
parentSpecifiers = parents!.map(symbol =>
50775081
some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//// [tests/cases/compiler/uniqueSymbolPropertyDeclarationEmit.ts] ////
2+
3+
//// [test.ts]
4+
import Op from './op';
5+
6+
export default function foo() {
7+
return {
8+
[Op.or]: [],
9+
};
10+
}
11+
12+
//// [op.ts]
13+
declare const Op: {
14+
readonly or: unique symbol;
15+
};
16+
17+
export default Op;
18+
19+
20+
//// [op.js]
21+
"use strict";
22+
exports.__esModule = true;
23+
exports["default"] = Op;
24+
//// [test.js]
25+
"use strict";
26+
var __importDefault = (this && this.__importDefault) || function (mod) {
27+
return (mod && mod.__esModule) ? mod : { "default": mod };
28+
};
29+
exports.__esModule = true;
30+
var op_1 = __importDefault(require("./op"));
31+
function foo() {
32+
var _a;
33+
return _a = {},
34+
_a[op_1["default"].or] = [],
35+
_a;
36+
}
37+
exports["default"] = foo;
38+
39+
40+
//// [op.d.ts]
41+
declare const Op: {
42+
readonly or: unique symbol;
43+
};
44+
export default Op;
45+
//// [test.d.ts]
46+
import Op from './op';
47+
export default function foo(): {
48+
[Op.or]: any[];
49+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// @noTypesAndSymbols: true
2+
// @esModuleInterop: true
3+
// @declaration: true
4+
5+
// @Filename: test.ts
6+
import Op from './op';
7+
8+
export default function foo() {
9+
return {
10+
[Op.or]: [],
11+
};
12+
}
13+
14+
// @Filename: op.ts
15+
declare const Op: {
16+
readonly or: unique symbol;
17+
};
18+
19+
export default Op;

0 commit comments

Comments
 (0)