diff --git a/Sources/LinkResolution/UCF.ResolutionTable.Search.swift b/Sources/LinkResolution/UCF.ResolutionTable.Search.swift index 9ca9c12b..83bf63e1 100644 --- a/Sources/LinkResolution/UCF.ResolutionTable.Search.swift +++ b/Sources/LinkResolution/UCF.ResolutionTable.Search.swift @@ -1,4 +1,5 @@ import InlineArray +import Symbols import UCF extension UCF.ResolutionTable @@ -9,15 +10,15 @@ extension UCF.ResolutionTable let predicate:UCF.Selector.Suffix? private - var selected:InlineArray + var selected:[Symbol.Decl: Overload] private - var rejected:[Overload] + var rejected:[Symbol.Decl: Overload] init(matching predicate:UCF.Selector.Suffix?) { self.predicate = predicate - self.selected = [] - self.rejected = [] + self.selected = [:] + self.rejected = [:] } } } @@ -26,43 +27,62 @@ extension UCF.ResolutionTable.Search mutating func add(_ candidates:InlineArray) { + // Because of the way `@_exported` paths are represented in the search tree, it is + // possible to encounter the same overload multiple times, due to namespace inference if let predicate:UCF.Selector.Suffix = self.predicate { for overload:Overload in candidates { - predicate ~= overload ? - self.selected.append(overload) : - self.rejected.append(overload) + guard predicate ~= overload + else + { + self.rejected[overload.id] = overload + continue + } + + self.selected[overload.id] = overload } } else { for overload:Overload in candidates { - self.selected.append(overload) + self.selected[overload.id] = overload } } } func any() -> UCF.Resolution? { - switch self.selected + guard + let overload:Overload = self.selected.values.first + else { - case .one(let overload): - .overload(overload) + return nil + } - case .some(let overloads): - overloads.isEmpty ? nil : .ambiguous(overloads, rejected: self.rejected) + if self.selected.count == 1 + { + return .overload(overload) + } + else + { + return .ambiguous(self.selected.values.sorted { $0.id < $1.id }, + rejected: self.rejected.values.sorted { $0.id < $1.id }) } } consuming func get() -> UCF.Resolution { - switch self.selected + if let overload:Overload = self.selected.values.first, self.selected.count == 1 + { + return .overload(overload) + } + else { - case .one(let overload): .overload(overload) - case .some(let overloads): .ambiguous(overloads, rejected: self.rejected) + return .ambiguous(self.selected.values.sorted { $0.id < $1.id }, + rejected: self.rejected.values.sorted { $0.id < $1.id }) } } } diff --git a/Sources/SymbolGraphBuilder/Builds/SSGC.Workspace.swift b/Sources/SymbolGraphBuilder/Builds/SSGC.Workspace.swift index 8b9aae45..748625fb 100644 --- a/Sources/SymbolGraphBuilder/Builds/SSGC.Workspace.swift +++ b/Sources/SymbolGraphBuilder/Builds/SSGC.Workspace.swift @@ -54,17 +54,25 @@ extension SSGC.Workspace public func build(package build:SSGC.PackageBuild, with swift:SSGC.Toolchain, + validation:SSGC.ValidationBehavior = .ignoreErrors, clean:Bool = true) throws -> SymbolGraphObject { - try self.build(some: build, toolchain: swift, status: nil, clean: clean) + try self.build(some: build, toolchain: swift, + status: nil, + logger: .init(validation: validation, file: nil), + clean: clean) } public func build(special build:SSGC.StandardLibraryBuild, with swift:SSGC.Toolchain, + validation:SSGC.ValidationBehavior = .ignoreErrors, clean:Bool = true) throws -> SymbolGraphObject { - try self.build(some: build, toolchain: swift, status: nil, clean: clean) + try self.build(some: build, toolchain: swift, + status: nil, + logger: .init(validation: validation, file: nil), + clean: clean) } } extension SSGC.Workspace diff --git a/Sources/SymbolGraphBuilderTests/Main.swift b/Sources/SymbolGraphBuilderTests/Main.swift index e9034016..4ec84b7c 100644 --- a/Sources/SymbolGraphBuilderTests/Main.swift +++ b/Sources/SymbolGraphBuilderTests/Main.swift @@ -153,6 +153,17 @@ enum Main:TestMain, TestBattery #endif + if let tests:TestGroup = tests / "Reexportation", + let _:SymbolGraphObject = (tests.do + { + try workspace.build( + package: .local(project: "TestPackages" / "swift-exportation"), + with: toolchain, + validation: .failOnErrors) + }) + { + } + group: if let tests:TestGroup = tests / "Local", let docs:SymbolGraphObject = (tests.do diff --git a/TestPackages/swift-exportation/Package.swift b/TestPackages/swift-exportation/Package.swift new file mode 100644 index 00000000..2f6d75cf --- /dev/null +++ b/TestPackages/swift-exportation/Package.swift @@ -0,0 +1,15 @@ +// swift-tools-version:5.10 +import PackageDescription + +let package:Package = .init(name: "Swift Unidoc Exportation Tests", + products: + [ + .library(name: "A", targets: ["A"]), + .library(name: "B", targets: ["B"]), + ], + targets: + [ + .target(name: "_A"), + .target(name: "A", dependencies: ["_A"]), + .target(name: "B", dependencies: ["A"]), + ]) diff --git a/TestPackages/swift-exportation/Sources/A/exports.swift b/TestPackages/swift-exportation/Sources/A/exports.swift new file mode 100644 index 00000000..a10fc020 --- /dev/null +++ b/TestPackages/swift-exportation/Sources/A/exports.swift @@ -0,0 +1 @@ +@_exported import _A diff --git a/TestPackages/swift-exportation/Sources/B/anchor.swift b/TestPackages/swift-exportation/Sources/B/anchor.swift new file mode 100644 index 00000000..e69de29b diff --git a/TestPackages/swift-exportation/Sources/B/docs.docc/Article.md b/TestPackages/swift-exportation/Sources/B/docs.docc/Article.md new file mode 100644 index 00000000..f2170ab8 --- /dev/null +++ b/TestPackages/swift-exportation/Sources/B/docs.docc/Article.md @@ -0,0 +1,7 @@ +# Exportation tests + +All of the following should be valid links: + +- ``A`` +- ``A.A`` +- ``_A.A`` diff --git a/TestPackages/swift-exportation/Sources/_A/A.swift b/TestPackages/swift-exportation/Sources/_A/A.swift new file mode 100644 index 00000000..fd26b701 --- /dev/null +++ b/TestPackages/swift-exportation/Sources/_A/A.swift @@ -0,0 +1,4 @@ +public +enum A +{ +}