Skip to content
Draft
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
101 changes: 46 additions & 55 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -16546,53 +16546,64 @@ func (c *Checker) getExportsOfModule(moduleSymbol *ast.Symbol) ast.SymbolTable {
return links.resolvedExports
}

type ExportCollision struct {
specifierText string
exportsWithDuplicate []*ast.Node
}

type ExportCollisionTable = map[string]*ExportCollision

func (c *Checker) getExportsOfModuleWorker(moduleSymbol *ast.Symbol) (exports ast.SymbolTable, typeOnlyExportStarMap map[string]*ast.Node) {
var visitedSymbols []*ast.Symbol
nonTypeOnlyNames := collections.NewSetWithSizeHint[string](len(moduleSymbol.Exports()))
// Modules reached through a non-type-only path. Their export names are removed from
// typeOnlyExportStarMap once the traversal is complete, since visiting a module via
// 'export *' overrides the type-onlyness its exports acquired through an 'export type *'.
var nonTypeOnlyModules []*ast.Symbol
// The ES6 spec permits export * declarations in a module to circularly reference the module itself. For example,
// module 'a' can 'export * from "b"' and 'b' can 'export * from "a"' without error.
var visit func(*ast.Symbol, *ast.Node, bool) ast.SymbolTable
visit = func(symbol *ast.Symbol, exportStar *ast.Node, isTypeOnly bool) ast.SymbolTable {
if !isTypeOnly && symbol != nil {
// Add non-type-only names before checking if we've visited this module,
// because we might have visited it via an 'export type *', and visiting
// again with 'export *' will override the type-onlyness of its exports.
for name := range symbol.Exports() {
nonTypeOnlyNames.Add(name)
}
nonTypeOnlyModules = append(nonTypeOnlyModules, symbol)
}
if symbol == nil || symbol.Exports() == nil || slices.Contains(visitedSymbols, symbol) {
return nil
}
visitedSymbols = append(visitedSymbols, symbol)
symbols := maps.Clone(symbol.Exports())
var symbols ast.SymbolTable
// All export * declarations are collected in an __export symbol by the binder
exportStars := symbol.Exports()[ast.InternalSymbolNameExportStar]
if exportStars != nil {
nestedSymbols := make(ast.SymbolTable)
lookupTable := make(ExportCollisionTable)
for _, node := range exportStars.Declarations() {
if exportStars == nil {
symbols = maps.Clone(symbol.Exports())
} else {
declarations := exportStars.Declarations()
exportedSymbols := make([]ast.SymbolTable, len(declarations))
largest := 0
for i, node := range declarations {
resolvedModule := c.resolveExternalModuleName(node, node.ModuleSpecifier(), false /*ignoreErrors*/, c.getTypeFromImportAttributes(ast.GetImportAttributes(node)))
exportedSymbols := visit(resolvedModule, node, isTypeOnly || node.IsTypeOnly())
c.extendExportSymbols(nestedSymbols, exportedSymbols, lookupTable, node)
}
for id, s := range lookupTable {
// It's not an error if the file with multiple `export *`s with duplicate names exports a member with that name itself
if id == ast.InternalSymbolNameExportEquals || len(s.exportsWithDuplicate) == 0 || symbols[id] != nil {
continue
}
for _, node := range s.exportsWithDuplicate {
c.addDiagnostic(createDiagnosticForNode(node, diagnostics.Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity, s.specifierText, id))
exportedSymbols[i] = visit(resolvedModule, node, isTypeOnly || node.IsTypeOnly())
largest = max(largest, len(exportedSymbols[i]))
}
// Re-exported tables often overlap, so size for the largest one and let the map grow past that.
symbols = make(ast.SymbolTable, len(symbol.Exports())+largest)
maps.Copy(symbols, symbol.Exports())
// The export * declaration that first contributed each name, so that a later declaration contributing
// a different symbol under that name can be reported. A single declaration cannot collide with itself.
var firstExportStar map[string]*ast.Node
if len(declarations) > 1 {
firstExportStar = make(map[string]*ast.Node, largest)
}
for i, node := range declarations {
for id, sourceSymbol := range exportedSymbols[i] {
if id == ast.InternalSymbolNameDefault {
continue
}
targetSymbol := symbols[id]
if targetSymbol == nil {
symbols[id] = sourceSymbol
if firstExportStar != nil {
firstExportStar[id] = node
}
} else if first := firstExportStar[id]; first != nil && id != ast.InternalSymbolNameExportEquals && c.resolveSymbol(targetSymbol) != c.resolveSymbol(sourceSymbol) {
// Names the module exports itself were in the table before any export * and have no first
// declaration, so it's not an error when they are also re-exported.
c.addDiagnostic(createDiagnosticForNode(node, diagnostics.Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity, scanner.GetTextOfNode(first.ModuleSpecifier()), id))
}
}
}
c.extendExportSymbols(symbols, nestedSymbols, nil, nil)
}
if exportStar != nil && exportStar.IsTypeOnly() {
if typeOnlyExportStarMap == nil {
Expand Down Expand Up @@ -16630,34 +16641,14 @@ func (c *Checker) getExportsOfModuleWorker(moduleSymbol *ast.Symbol) (exports as
}
}
}
for name := range nonTypeOnlyNames.Keys() {
delete(typeOnlyExportStarMap, name)
}
return exports, typeOnlyExportStarMap
}

/**
* Extends one symbol table with another while collecting information on name collisions for error message generation into the `lookupTable` argument
* Not passing `lookupTable` and `exportNode` disables this collection, and just extends the tables
*/
func (c *Checker) extendExportSymbols(target ast.SymbolTable, source ast.SymbolTable, lookupTable ExportCollisionTable, exportNode *ast.Node) {
for id, sourceSymbol := range source {
if id == ast.InternalSymbolNameDefault {
continue
}
targetSymbol := target[id]
if targetSymbol == nil {
target[id] = sourceSymbol
if lookupTable != nil && exportNode != nil {
lookupTable[id] = &ExportCollision{
specifierText: scanner.GetTextOfNode(exportNode.ModuleSpecifier()),
}
if typeOnlyExportStarMap != nil {
for _, symbol := range nonTypeOnlyModules {
for name := range symbol.Exports() {
delete(typeOnlyExportStarMap, name)
}
} else if lookupTable != nil && exportNode != nil && c.resolveSymbol(targetSymbol) != c.resolveSymbol(sourceSymbol) {
s := lookupTable[id]
s.exportsWithDuplicate = append(s.exportsWithDuplicate, exportNode)
}
}
return exports, typeOnlyExportStarMap
}

func (c *Checker) ResolveAlias(symbol *ast.Symbol) (*ast.Symbol, bool) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
consumer.ts(9,58): error TS1362: 'a5' cannot be used as a value because it was exported using 'export type'.
consumer.ts(9,70): error TS1362: 'b6' cannot be used as a value because it was exported using 'export type'.
localShadows.ts(3,1): error TS2308: Module "./a" has already exported a member named 'SharedType'. Consider explicitly re-exporting to resolve the ambiguity.
threeStarsCollide.ts(3,1): error TS2308: Module "./a" has already exported a member named 'SharedType'. Consider explicitly re-exporting to resolve the ambiguity.
threeStarsCollide.ts(3,1): error TS2308: Module "./a" has already exported a member named 'shared'. Consider explicitly re-exporting to resolve the ambiguity.
threeStarsCollide.ts(4,1): error TS2308: Module "./a" has already exported a member named 'shared'. Consider explicitly re-exporting to resolve the ambiguity.
twoStarsCollide.ts(3,1): error TS2308: Module "./a" has already exported a member named 'SharedType'. Consider explicitly re-exporting to resolve the ambiguity.
twoStarsCollide.ts(3,1): error TS2308: Module "./a" has already exported a member named 'shared'. Consider explicitly re-exporting to resolve the ambiguity.
typeOnlyThenValue.ts(2,1): error TS2308: Module "./a" has already exported a member named 'SharedType'. Consider explicitly re-exporting to resolve the ambiguity.
typeOnlyThenValue.ts(2,1): error TS2308: Module "./a" has already exported a member named 'shared'. Consider explicitly re-exporting to resolve the ambiguity.
valueThenTypeOnly.ts(2,1): error TS2308: Module "./a" has already exported a member named 'SharedType'. Consider explicitly re-exporting to resolve the ambiguity.
valueThenTypeOnly.ts(2,1): error TS2308: Module "./a" has already exported a member named 'shared'. Consider explicitly re-exporting to resolve the ambiguity.


==== a.ts (0 errors) ====
export const onlyA = 1;
export const shared = "a";
export interface SharedType { fromA: true }
export default "a";

==== b.ts (0 errors) ====
export const onlyB = 2;
export const shared = "b";
export interface SharedType { fromB: true }
export default "b";

==== b2.ts (0 errors) ====
export const shared = "b2";

==== sameSymbol.ts (0 errors) ====
export { shared } from "./a";

==== twoStarsCollide.ts (2 errors) ====
// 'shared' and 'SharedType' come from both modules with different meanings.
export * from "./a";
export * from "./b";
~~~~~~~~~~~~~~~~~~~~
!!! error TS2308: Module "./a" has already exported a member named 'SharedType'. Consider explicitly re-exporting to resolve the ambiguity.
~~~~~~~~~~~~~~~~~~~~
!!! error TS2308: Module "./a" has already exported a member named 'shared'. Consider explicitly re-exporting to resolve the ambiguity.

==== threeStarsCollide.ts (3 errors) ====
// Both later declarations are reported against the first one.
export * from "./a";
export * from "./b";
~~~~~~~~~~~~~~~~~~~~
!!! error TS2308: Module "./a" has already exported a member named 'SharedType'. Consider explicitly re-exporting to resolve the ambiguity.
~~~~~~~~~~~~~~~~~~~~
!!! error TS2308: Module "./a" has already exported a member named 'shared'. Consider explicitly re-exporting to resolve the ambiguity.
export * from "./b2";
~~~~~~~~~~~~~~~~~~~~~
!!! error TS2308: Module "./a" has already exported a member named 'shared'. Consider explicitly re-exporting to resolve the ambiguity.

==== sameSymbolTwice.ts (0 errors) ====
// Reaching the same symbol through two declarations is not a collision.
export * from "./a";
export * from "./sameSymbol";

==== localShadows.ts (1 errors) ====
// A module's own export takes precedence and silences the collision for shared, but not for SharedType.
export * from "./a";
export * from "./b";
~~~~~~~~~~~~~~~~~~~~
!!! error TS2308: Module "./a" has already exported a member named 'SharedType'. Consider explicitly re-exporting to resolve the ambiguity.
export const shared = "local";

==== singleStar.ts (0 errors) ====
export * from "./a";

==== typeOnlyThenValue.ts (2 errors) ====
export type * from "./a";
export * from "./b";
~~~~~~~~~~~~~~~~~~~~
!!! error TS2308: Module "./a" has already exported a member named 'SharedType'. Consider explicitly re-exporting to resolve the ambiguity.
~~~~~~~~~~~~~~~~~~~~
!!! error TS2308: Module "./a" has already exported a member named 'shared'. Consider explicitly re-exporting to resolve the ambiguity.

==== valueThenTypeOnly.ts (2 errors) ====
export * from "./a";
export type * from "./b";
~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2308: Module "./a" has already exported a member named 'SharedType'. Consider explicitly re-exporting to resolve the ambiguity.
~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2308: Module "./a" has already exported a member named 'shared'. Consider explicitly re-exporting to resolve the ambiguity.

==== chained.ts (0 errors) ====
export * from "./twoStarsCollide";
export * from "./singleStar";

==== consumer.ts (2 errors) ====
import { onlyA, onlyB, shared as s1 } from "./twoStarsCollide";
import { shared as s2 } from "./sameSymbolTwice";
import { shared as s3 } from "./localShadows";
import { shared as s4, onlyA as a4 } from "./singleStar";
import { onlyA as a5, onlyB as b5 } from "./typeOnlyThenValue";
import { onlyA as a6, onlyB as b6 } from "./valueThenTypeOnly";
import { onlyA as a7, onlyB as b7, shared as s7 } from "./chained";
import type { SharedType } from "./twoStarsCollide";
export const values = [onlyA, onlyB, s1, s2, s3, s4, a4, a5, b5, a6, b6, a7, b7, s7];
~~
!!! error TS1362: 'a5' cannot be used as a value because it was exported using 'export type'.
!!! related TS1377 typeOnlyThenValue.ts:1:1: 'a5' was exported here.
~~
!!! error TS1362: 'b6' cannot be used as a value because it was exported using 'export type'.
!!! related TS1377 valueThenTypeOnly.ts:2:1: 'b6' was exported here.
export let t: SharedType | undefined;

Loading