Skip to content

Commit 61f67ae

Browse files
use SymbolKit's unified graph overload grouping
rdar://128624412
1 parent e400d82 commit 61f67ae

File tree

3 files changed

+111
-25
lines changed

3 files changed

+111
-25
lines changed

Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy.swift

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ struct PathHierarchy {
105105
if let language {
106106
moduleNode.languages.insert(language)
107107
}
108-
108+
109109
var nodes: [String: Node] = [:]
110110
nodes.reserveCapacity(graph.symbols.count)
111111
for (id, symbol) in graph.symbols {
@@ -120,16 +120,6 @@ struct PathHierarchy {
120120
} else {
121121
assert(!symbol.pathComponents.isEmpty, "A symbol should have at least its own name in its path components.")
122122

123-
if symbol.identifier.precise.hasSuffix(SymbolGraph.Symbol.overloadGroupIdentifierSuffix),
124-
loader.unifiedGraphs[moduleNode.name]?.symbols.keys.contains(symbol.identifier.precise) != true {
125-
// Overload groups can be discarded in the unified symbol graph collector if
126-
// they don't reflect the default overload across all platforms. In this
127-
// case, we don't want to add these nodes to the path hierarchy since
128-
// they've been discarded from the unified graph that's used to generate
129-
// documentation nodes.
130-
continue
131-
}
132-
133123
let node = Node(symbol: symbol, name: symbol.pathComponents.last!)
134124
// Disfavor synthesized symbols when they collide with other symbol with the same path.
135125
// FIXME: Get information about synthesized symbols from SymbolKit https://github.com/apple/swift-docc-symbolkit/issues/58
@@ -148,13 +138,6 @@ struct PathHierarchy {
148138
}
149139
}
150140

151-
for relationship in graph.relationships where relationship.kind == .overloadOf {
152-
// An 'overloadOf' relationship points from symbol -> group. We want to disfavor the
153-
// individual overload symbols in favor of resolving links to their overload group
154-
// symbol.
155-
nodes[relationship.source]?.specialBehaviors.formUnion([.disfavorInLinkCollision, .excludeFromAutomaticCuration])
156-
}
157-
158141
// If there are multiple symbol graphs (for example for different source languages or platforms) then the nodes may have already been added to the hierarchy.
159142
var topLevelCandidates = nodes.filter { _, node in node.parent == nil }
160143
for relationship in graph.relationships where relationship.kind.formsHierarchy {
@@ -283,7 +266,43 @@ struct PathHierarchy {
283266
parent.add(symbolChild: node)
284267
}
285268
}
286-
269+
270+
// Overload group don't exist in the individual symbol graphs.
271+
// Since overload groups don't change the _structure_ of the path hierarchy, we can add them after after all symbols for all platforms have already been added.
272+
for unifiedGraph in loader.unifiedGraphs.values {
273+
// Create nodes for all the overload groups
274+
let overloadGroupNodes: [String: Node] = unifiedGraph.overloadGroupSymbols.reduce(into: [:]) { acc, uniqueID in
275+
assert(allNodes[uniqueID] == nil,
276+
"Overload group ID \(uniqueID) already has a symbol node in the hierarchy: \(allNodes[uniqueID]!.map(\.name).sorted().joined(separator: ","))")
277+
guard let unifiedSymbol = unifiedGraph.symbols[uniqueID] else { return }
278+
guard let symbol = unifiedSymbol.defaultSymbol else {
279+
fatalError("Overload group \(uniqueID) doesn't have a default symbol.")
280+
}
281+
acc[uniqueID] = Node(symbol: symbol, name: symbol.pathComponents.last!)
282+
}
283+
284+
for relationship in unifiedGraph.relationshipsByLanguage.flatMap(\.value) where relationship.kind == .overloadOf {
285+
guard let groupNode = overloadGroupNodes[relationship.target], let overloadedSymbolNodes = allNodes[relationship.source] else {
286+
continue
287+
}
288+
289+
for overloadedSymbolNode in overloadedSymbolNodes {
290+
// We want to disfavor the individual overload symbols in favor of resolving links to their overload group symbol.
291+
overloadedSymbolNode.specialBehaviors.formUnion([.disfavorInLinkCollision, .excludeFromAutomaticCuration])
292+
293+
guard let parent = overloadedSymbolNode.parent else { continue }
294+
295+
assert(groupNode.parent == nil || groupNode.parent === parent, """
296+
Unexpectedly grouped symbols with different locations in the symbol hierarchy:
297+
Group ID: \(groupNode.symbol!.identifier.precise)
298+
Locations: \(Set(overloadedSymbolNodes.map { $0.symbol!.pathComponents.joined(separator: "/") }.sorted()))
299+
""")
300+
parent.add(symbolChild: groupNode)
301+
}
302+
assert(groupNode.parent != nil, "Unexpectedly found no location in the hierarchy for overload group \(relationship.source)")
303+
}
304+
}
305+
287306
assert(
288307
allNodes.allSatisfy({ $0.value[0].parent != nil || roots[$0.key] != nil }), """
289308
Every node should either have a parent node or be a root node. \

Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,6 @@ struct SymbolGraphLoader {
7878

7979
configureSymbolGraph?(&symbolGraph)
8080

81-
if FeatureFlags.current.isExperimentalOverloadedSymbolPresentationEnabled {
82-
symbolGraph.createOverloadGroupSymbols()
83-
}
84-
8581
let (moduleName, isMainSymbolGraph) = Self.moduleNameFor(symbolGraph, at: symbolGraphURL)
8682
// If the bundle provides availability defaults add symbol availability data.
8783
self.addDefaultAvailability(to: &symbolGraph, moduleName: moduleName)
@@ -149,8 +145,10 @@ struct SymbolGraphLoader {
149145
}
150146

151147
self.symbolGraphs = loadedGraphs.mapValues(\.graph)
152-
(self.unifiedGraphs, self.graphLocations) = graphLoader.finishLoading()
153-
148+
(self.unifiedGraphs, self.graphLocations) = graphLoader.finishLoading(
149+
createOverloadGroups: FeatureFlags.current.isExperimentalOverloadedSymbolPresentationEnabled
150+
)
151+
154152
for var unifiedGraph in unifiedGraphs.values {
155153
var defaultUnavailablePlatforms = [PlatformName]()
156154
var defaultAvailableInformation = [DefaultAvailability.ModuleAvailability]()

Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5279,6 +5279,75 @@ let expected = """
52795279
}
52805280
}
52815281

5282+
func testContextGeneratesOverloadGroupsForDisjointOverloads() throws {
5283+
enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled)
5284+
5285+
let symbolKind = try XCTUnwrap(SymbolGraph.Symbol.KindIdentifier.allCases.filter({ $0.isOverloadableKind }).first)
5286+
5287+
let tempURL = try createTempFolder(content: [
5288+
Folder(name: "unit-test.docc", content: [
5289+
JSONFile(name: "ModuleName-macos.symbols.json", content: makeSymbolGraph(
5290+
moduleName: "ModuleName",
5291+
platform: .init(operatingSystem: .init(name: "macosx")),
5292+
symbols: [
5293+
makeSymbol(identifier: "symbol-1", kind: symbolKind),
5294+
])),
5295+
JSONFile(name: "ModuleName-ios.symbols.json", content: makeSymbolGraph(
5296+
moduleName: "ModuleName",
5297+
platform: .init(operatingSystem: .init(name: "ios")),
5298+
symbols: [
5299+
makeSymbol(identifier: "symbol-2", kind: symbolKind),
5300+
])),
5301+
])
5302+
])
5303+
let (_, bundle, context) = try loadBundle(from: tempURL)
5304+
let moduleReference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/ModuleName", sourceLanguage: .swift)
5305+
5306+
let overloadGroupNode: DocumentationNode
5307+
let overloadGroupSymbol: Symbol
5308+
let overloadGroupReferences: Symbol.Overloads
5309+
5310+
switch context.resolve(.unresolved(.init(topicURL: .init(symbolPath: "SymbolName"))), in: moduleReference, fromSymbolLink: true) {
5311+
case let .failure(_, errorMessage):
5312+
XCTFail("Could not resolve overload group page. Error message: \(errorMessage)")
5313+
return
5314+
case let .success(overloadGroupReference):
5315+
overloadGroupNode = try context.entity(with: overloadGroupReference)
5316+
overloadGroupSymbol = try XCTUnwrap(overloadGroupNode.semantic as? Symbol)
5317+
overloadGroupReferences = try XCTUnwrap(overloadGroupSymbol.overloadsVariants.firstValue)
5318+
5319+
XCTAssertEqual(overloadGroupReferences.displayIndex, 0)
5320+
5321+
let unifiedSymbol = try XCTUnwrap(overloadGroupNode.unifiedSymbol)
5322+
XCTAssertEqual(unifiedSymbol.uniqueIdentifier, "symbol-1" + SymbolGraph.Symbol.overloadGroupIdentifierSuffix)
5323+
}
5324+
5325+
let overloadedReferences = try ["symbol-1", "symbol-2"]
5326+
.map { try XCTUnwrap(context.documentationCache.reference(symbolID: $0)) }
5327+
5328+
for (index, reference) in overloadedReferences.indexed() {
5329+
let overloadedDocumentationNode = try XCTUnwrap(context.documentationCache[reference])
5330+
let overloadedSymbol = try XCTUnwrap(overloadedDocumentationNode.semantic as? Symbol)
5331+
5332+
let overloads = try XCTUnwrap(overloadedSymbol.overloadsVariants.firstValue)
5333+
5334+
// Make sure that each symbol contains all of its sibling overloads.
5335+
XCTAssertEqual(overloads.references.count, overloadedReferences.count - 1)
5336+
for (otherIndex, otherReference) in overloadedReferences.indexed() where otherIndex != index {
5337+
XCTAssert(overloads.references.contains(otherReference))
5338+
}
5339+
5340+
if overloads.displayIndex == 0 {
5341+
// The first declaration in the display list should be the same declaration as
5342+
// the overload group page
5343+
XCTAssertEqual(overloadedSymbol.declaration.first?.value.declarationFragments, overloadGroupSymbol.declaration.first?.value.declarationFragments)
5344+
} else {
5345+
// Otherwise, this reference should also be referenced by the overload group
5346+
XCTAssert(overloadGroupReferences.references.contains(reference))
5347+
}
5348+
}
5349+
}
5350+
52825351
// A test helper that creates a symbol with a given identifier and kind.
52835352
private func makeSymbol(
52845353
name: String = "SymbolName",

0 commit comments

Comments
 (0)