Skip to content

Commit 081ad56

Browse files
use SymbolKit's unified graph overload grouping (#986)
rdar://128624412
1 parent 3d58514 commit 081ad56

File tree

4 files changed

+112
-26
lines changed

4 files changed

+112
-26
lines changed

Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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/swiftlang/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
@@ -5276,6 +5276,75 @@ let expected = """
52765276
}
52775277
}
52785278

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

0 commit comments

Comments
 (0)