From 576a7333787133b08ab1af72c2409ef869b2ab1e Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 30 May 2018 17:52:59 -0700 Subject: [PATCH] For type emit, walk non-parent containers when those containers have aliases leading to the target (#24507) --- src/compiler/checker.ts | 46 +++++++++++++++++-- ...ltImportsCanPaintCrossModuleDeclaration.js | 37 +++++++++++++++ ...ortsCanPaintCrossModuleDeclaration.symbols | 26 +++++++++++ ...mportsCanPaintCrossModuleDeclaration.types | 27 +++++++++++ .../reference/importTypeGenericTypes.types | 4 +- .../baselines/reference/importTypeLocal.types | 4 +- ...ltImportsCanPaintCrossModuleDeclaration.ts | 12 +++++ 7 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 tests/baselines/reference/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.js create mode 100644 tests/baselines/reference/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.symbols create mode 100644 tests/baselines/reference/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.types create mode 100644 tests/cases/compiler/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 033e332e068..7dd798a935d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2533,6 +2533,46 @@ namespace ts { return getMergedSymbol(symbol.parent && getLateBoundSymbol(symbol.parent)); } + /** + * 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 getContainerOfSymbol(symbol: Symbol): Symbol | undefined { + const container = getParentOfSymbol(symbol); + if (container) { + return container; + } + const candidate = forEach(symbol.declarations, d => !isAmbientModule(d) && d.parent && hasNonGlobalAugmentationExternalModuleSymbol(d.parent) ? getSymbolOfNode(d.parent) : undefined); + if (!candidate) { + return undefined; + } + const alias = getAliasForSymbolInContainer(candidate, symbol); + return alias ? candidate : undefined; + } + + function getAliasForSymbolInContainer(container: Symbol, symbol: Symbol) { + if (container === getParentOfSymbol(symbol)) { + // fast path, `symbol` is either already the alias or isn't aliased + return symbol; + } + const exports = getExportsOfSymbol(container); + const quick = exports.get(symbol.escapedName); + if (quick && symbolRefersToTarget(quick)) { + return quick; + } + return forEachEntry(exports, exported => { + if (symbolRefersToTarget(exported)) { + return exported; + } + }); + + function symbolRefersToTarget(s: Symbol) { + if (s === symbol || resolveSymbol(s) === symbol || resolveSymbol(s) === resolveSymbol(symbol)) { + return s; + } + } + } + function getExportSymbolOfValueSymbolIfExported(symbol: Symbol): Symbol; function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined; function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined { @@ -2838,7 +2878,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 meaningToLook = getQualifiedLeftMeaning(meaning); - symbol = getParentOfSymbol(symbol); + symbol = getContainerOfSymbol(symbol); } // This could be a symbol that is not exported in the external module @@ -3729,12 +3769,12 @@ namespace ts { needsQualification(accessibleSymbolChain[0], context.enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { // Go up and add our parent. - const parent = getParentOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol); + const parent = getContainerOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol); if (parent) { const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); if (parentChain) { parentSymbol = parent; - accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [symbol]); + accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [getAliasForSymbolInContainer(parent, symbol) || symbol]); } } } diff --git a/tests/baselines/reference/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.js b/tests/baselines/reference/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.js new file mode 100644 index 00000000000..96a5bfcb068 --- /dev/null +++ b/tests/baselines/reference/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.js @@ -0,0 +1,37 @@ +//// [tests/cases/compiler/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.ts] //// + +//// [color.ts] +interface Color { + c: string; +} +export default Color; +//// [file1.ts] +import Color from "./color"; +export declare function styled(): Color; +//// [file2.ts] +import { styled } from "./file1"; +export const A = styled(); + +//// [color.js] +"use strict"; +exports.__esModule = true; +//// [file1.js] +"use strict"; +exports.__esModule = true; +//// [file2.js] +"use strict"; +exports.__esModule = true; +var file1_1 = require("./file1"); +exports.A = file1_1.styled(); + + +//// [color.d.ts] +interface Color { + c: string; +} +export default Color; +//// [file1.d.ts] +import Color from "./color"; +export declare function styled(): Color; +//// [file2.d.ts] +export declare const A: import("./color").default; diff --git a/tests/baselines/reference/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.symbols b/tests/baselines/reference/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.symbols new file mode 100644 index 00000000000..4b1cd3b370e --- /dev/null +++ b/tests/baselines/reference/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.symbols @@ -0,0 +1,26 @@ +=== tests/cases/compiler/color.ts === +interface Color { +>Color : Symbol(Color, Decl(color.ts, 0, 0)) + + c: string; +>c : Symbol(Color.c, Decl(color.ts, 0, 17)) +} +export default Color; +>Color : Symbol(Color, Decl(color.ts, 0, 0)) + +=== tests/cases/compiler/file1.ts === +import Color from "./color"; +>Color : Symbol(Color, Decl(file1.ts, 0, 6)) + +export declare function styled(): Color; +>styled : Symbol(styled, Decl(file1.ts, 0, 28)) +>Color : Symbol(Color, Decl(file1.ts, 0, 6)) + +=== tests/cases/compiler/file2.ts === +import { styled } from "./file1"; +>styled : Symbol(styled, Decl(file2.ts, 0, 8)) + +export const A = styled(); +>A : Symbol(A, Decl(file2.ts, 1, 12)) +>styled : Symbol(styled, Decl(file2.ts, 0, 8)) + diff --git a/tests/baselines/reference/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.types b/tests/baselines/reference/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.types new file mode 100644 index 00000000000..ffcf611af8c --- /dev/null +++ b/tests/baselines/reference/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/color.ts === +interface Color { +>Color : Color + + c: string; +>c : string +} +export default Color; +>Color : Color + +=== tests/cases/compiler/file1.ts === +import Color from "./color"; +>Color : any + +export declare function styled(): Color; +>styled : () => Color +>Color : Color + +=== tests/cases/compiler/file2.ts === +import { styled } from "./file1"; +>styled : () => import("tests/cases/compiler/color").default + +export const A = styled(); +>A : import("tests/cases/compiler/color").default +>styled() : import("tests/cases/compiler/color").default +>styled : () => import("tests/cases/compiler/color").default + diff --git a/tests/baselines/reference/importTypeGenericTypes.types b/tests/baselines/reference/importTypeGenericTypes.types index 19611db6963..dc7dff5e6f6 100644 --- a/tests/baselines/reference/importTypeGenericTypes.types +++ b/tests/baselines/reference/importTypeGenericTypes.types @@ -89,9 +89,9 @@ export const x: import("./foo")<{x: number}> = { x: 0, y: 0, data: {x: 12} }; >12 : 12 export let y: import("./foo2").Bar.I<{x: number}> = { a: "", b: 0, data: {x: 12} }; ->y : Bar.I<{ x: number; }> +>y : import("tests/cases/conformance/types/import/foo2").Bar.I<{ x: number; }> >Bar : any ->I : Bar.I +>I : import("tests/cases/conformance/types/import/foo2").Bar.I >x : number >{ a: "", b: 0, data: {x: 12} } : { a: string; b: number; data: { x: number; }; } >a : string diff --git a/tests/baselines/reference/importTypeLocal.types b/tests/baselines/reference/importTypeLocal.types index ed7f28b2910..e4791b52ee9 100644 --- a/tests/baselines/reference/importTypeLocal.types +++ b/tests/baselines/reference/importTypeLocal.types @@ -66,9 +66,9 @@ export const x: import("./foo") = { x: 0, y: 0 }; >0 : 0 export let y: import("./foo2").Bar.I = { a: "", b: 0 }; ->y : Bar.I +>y : import("tests/cases/conformance/types/import/foo2").Bar.I >Bar : any ->I : Bar.I +>I : import("tests/cases/conformance/types/import/foo2").Bar.I >{ a: "", b: 0 } : { a: string; b: number; } >a : string >"" : "" diff --git a/tests/cases/compiler/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.ts b/tests/cases/compiler/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.ts new file mode 100644 index 00000000000..5341faa78be --- /dev/null +++ b/tests/cases/compiler/allowSyntheticDefaultImportsCanPaintCrossModuleDeclaration.ts @@ -0,0 +1,12 @@ +// @declaration: true +// @filename: color.ts +interface Color { + c: string; +} +export default Color; +// @filename: file1.ts +import Color from "./color"; +export declare function styled(): Color; +// @filename: file2.ts +import { styled } from "./file1"; +export const A = styled(); \ No newline at end of file