Skip to content

Commit 2e71ee0

Browse files
use unified overload data for initial overload variants (#907)
rdar://127130136
1 parent aa0a99e commit 2e71ee0

File tree

6 files changed

+255
-20
lines changed

6 files changed

+255
-20
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/DocumentationContext.swift

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,8 +1161,6 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
11611161

11621162
// For inherited symbols we remove the source docs (if inheriting docs is disabled) before creating their documentation nodes.
11631163
for (_, relationships) in unifiedSymbolGraph.relationshipsByLanguage {
1164-
var overloadGroups = [String: [String]]()
1165-
11661164
for relationship in relationships {
11671165
// Check for an origin key.
11681166
if let sourceOrigin = relationship[mixin: SymbolGraph.Relationship.SourceOrigin.self],
@@ -1176,15 +1174,18 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
11761174
localCache: documentationCache,
11771175
moduleName: moduleName
11781176
)
1179-
} else if relationship.kind == .overloadOf {
1180-
// An 'overloadOf' relationship points from symbol -> group
1181-
overloadGroups[relationship.target, default: []].append(relationship.source)
11821177
}
11831178
}
1184-
1185-
addOverloadGroupReferences(overloadGroups: overloadGroups)
11861179
}
1187-
1180+
1181+
let overloadGroups: [String: Set<String>] =
1182+
unifiedSymbolGraph.relationshipsByLanguage.values.flatMap({
1183+
$0.filter { $0.kind == .overloadOf }
1184+
}).reduce(into: [:], { acc, relationship in
1185+
acc[relationship.target, default: []].insert(relationship.source)
1186+
})
1187+
addOverloadGroupReferences(overloadGroups: overloadGroups)
1188+
11881189
if let rootURL = symbolGraphLoader.mainModuleURL(forModule: moduleName), let rootModule = unifiedSymbolGraph.moduleData[rootURL] {
11891190
addPreparedSymbolToContext(
11901191
preparedSymbolData(.init(fromSingleSymbol: moduleSymbol, module: rootModule, isMainGraph: true), reference: moduleReference, module: rootModule, moduleReference: moduleReference, fileURL: fileURL)
@@ -2395,7 +2396,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
23952396
return automaticallyCuratedSymbols
23962397
}
23972398

2398-
private func addOverloadGroupReferences(overloadGroups: [String: [String]]) {
2399+
private func addOverloadGroupReferences(overloadGroups: [String: Set<String>]) {
23992400
guard FeatureFlags.current.isExperimentalOverloadedSymbolPresentationEnabled else {
24002401
return
24012402
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,17 @@ struct PathHierarchy {
119119
existingNode.languages.insert(language!) // If we have symbols in this graph we have a language as well
120120
} else {
121121
assert(!symbol.pathComponents.isEmpty, "A symbol should have at least its own name in its path components.")
122+
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+
122133
let node = Node(symbol: symbol, name: symbol.pathComponents.last!)
123134
// Disfavor synthesized symbols when they collide with other symbol with the same path.
124135
// FIXME: Get information about synthesized symbols from SymbolKit https://github.com/apple/swift-docc-symbolkit/issues/58

Sources/SwiftDocC/Model/DocumentationNode.swift

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -226,14 +226,9 @@ public struct DocumentationNode {
226226
}
227227

228228
let overloadVariants = DocumentationDataVariants(
229-
symbolData: unifiedSymbol.mixins,
230-
platformName: platformName
231-
) { mixins -> Symbol.Overloads? in
232-
guard let overloadData = mixins[SymbolGraph.Symbol.OverloadData.mixinKey] as? SymbolGraph.Symbol.OverloadData else {
233-
return nil
234-
}
235-
return .init(references: [], displayIndex: overloadData.overloadGroupIndex)
236-
}
229+
swiftVariant: unifiedSymbol.unifiedOverloadData.map { overloadData in
230+
Symbol.Overloads(references: [], displayIndex: overloadData.overloadGroupIndex)
231+
})
237232

238233
var languages = Set([reference.sourceLanguage])
239234
var operatingSystemName = platformName.map({ Set([$0]) }) ?? []

Sources/SwiftDocCTestUtilities/SymbolGraphCreation.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,20 @@ import XCTest
1414
import SymbolKit
1515

1616
extension XCTestCase {
17-
public func makeSymbolGraph(moduleName: String, symbols: [SymbolGraph.Symbol] = [], relationships: [SymbolGraph.Relationship] = []) -> SymbolGraph {
17+
public func makeSymbolGraph(
18+
moduleName: String,
19+
platform: SymbolGraph.Platform = .init(),
20+
symbols: [SymbolGraph.Symbol] = [],
21+
relationships: [SymbolGraph.Relationship] = []
22+
) -> SymbolGraph {
1823
return SymbolGraph(
1924
metadata: SymbolGraph.Metadata(
2025
formatVersion: SymbolGraph.SemanticVersion(major: 0, minor: 6, patch: 0),
2126
generator: "unit-test"
2227
),
2328
module: SymbolGraph.Module(
2429
name: moduleName,
25-
platform: SymbolGraph.Platform(architecture: nil, vendor: nil, operatingSystem: nil)
30+
platform: platform
2631
),
2732
symbols: symbols,
2833
relationships: relationships

Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4919,6 +4919,229 @@ let expected = """
49194919
"Unknown feature flag in Info.plist: 'ExperimenalOverloadedSymbolPresentation'. Possible suggestions: 'ExperimentalOverloadedSymbolPresentation'")
49204920
}
49214921

4922+
func testContextGeneratesUnifiedOverloadGroupsAcrossPlatforms() throws {
4923+
enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled)
4924+
4925+
let symbolKind = try XCTUnwrap(SymbolGraph.Symbol.KindIdentifier.allCases.filter({ $0.isOverloadableKind }).first)
4926+
4927+
let tempURL = try createTempFolder(content: [
4928+
Folder(name: "unit-test.docc", content: [
4929+
JSONFile(name: "ModuleName-macos.symbols.json", content: makeSymbolGraph(
4930+
moduleName: "ModuleName",
4931+
platform: .init(operatingSystem: .init(name: "macosx")),
4932+
symbols: [
4933+
makeSymbol(identifier: "symbol-1", kind: symbolKind),
4934+
makeSymbol(identifier: "symbol-2", kind: symbolKind),
4935+
])),
4936+
JSONFile(name: "ModuleName-ios.symbols.json", content: makeSymbolGraph(
4937+
moduleName: "ModuleName",
4938+
platform: .init(operatingSystem: .init(name: "ios")),
4939+
symbols: [
4940+
makeSymbol(identifier: "symbol-2", kind: symbolKind),
4941+
makeSymbol(identifier: "symbol-3", kind: symbolKind),
4942+
])),
4943+
])
4944+
])
4945+
let (_, bundle, context) = try loadBundle(from: tempURL)
4946+
let moduleReference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/ModuleName", sourceLanguage: .swift)
4947+
4948+
let overloadGroupNode: DocumentationNode
4949+
let overloadGroupSymbol: Symbol
4950+
let overloadGroupReferences: Symbol.Overloads
4951+
4952+
// There should only be one overload group for `SymbolName` - the one from the iOS symbol
4953+
// graph should have been removed during graph collection.
4954+
switch context.resolve(.unresolved(.init(topicURL: .init(symbolPath: "SymbolName"))), in: moduleReference, fromSymbolLink: true) {
4955+
case let .failure(_, errorMessage):
4956+
XCTFail("Could not resolve overload group page. Error message: \(errorMessage)")
4957+
return
4958+
case let .success(overloadGroupReference):
4959+
overloadGroupNode = try context.entity(with: overloadGroupReference)
4960+
overloadGroupSymbol = try XCTUnwrap(overloadGroupNode.semantic as? Symbol)
4961+
overloadGroupReferences = try XCTUnwrap(overloadGroupSymbol.overloadsVariants.firstValue)
4962+
4963+
XCTAssertEqual(overloadGroupReferences.displayIndex, 0)
4964+
4965+
let unifiedSymbol = try XCTUnwrap(overloadGroupNode.unifiedSymbol)
4966+
XCTAssertEqual(unifiedSymbol.uniqueIdentifier, "symbol-1" + SymbolGraph.Symbol.overloadGroupIdentifierSuffix)
4967+
}
4968+
4969+
let overloadedReferences = try ["symbol-1", "symbol-2", "symbol-3"]
4970+
.map { try XCTUnwrap(context.documentationCache.reference(symbolID: $0)) }
4971+
4972+
for (index, reference) in overloadedReferences.indexed() {
4973+
let overloadedDocumentationNode = try XCTUnwrap(context.documentationCache[reference])
4974+
let overloadedSymbol = try XCTUnwrap(overloadedDocumentationNode.semantic as? Symbol)
4975+
4976+
let overloads = try XCTUnwrap(overloadedSymbol.overloadsVariants.firstValue)
4977+
4978+
// Make sure that each symbol contains all of its sibling overloads.
4979+
XCTAssertEqual(overloads.references.count, overloadedReferences.count - 1)
4980+
for (otherIndex, otherReference) in overloadedReferences.indexed() where otherIndex != index {
4981+
XCTAssert(overloads.references.contains(otherReference))
4982+
}
4983+
4984+
if overloads.displayIndex == 0 {
4985+
// The first declaration in the display list should be the same declaration as
4986+
// the overload group page
4987+
XCTAssertEqual(overloadedSymbol.declaration.first?.value.declarationFragments, overloadGroupSymbol.declaration.first?.value.declarationFragments)
4988+
} else {
4989+
// Otherwise, this reference should also be referenced by the overload group
4990+
XCTAssert(overloadGroupReferences.references.contains(reference))
4991+
}
4992+
}
4993+
}
4994+
4995+
func testContextGeneratesOverloadGroupsWhenOnePlatformHasNoOverloads() throws {
4996+
enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled)
4997+
4998+
let symbolKind = try XCTUnwrap(SymbolGraph.Symbol.KindIdentifier.allCases.filter({ $0.isOverloadableKind }).first)
4999+
5000+
// This situation used to crash. The macOS symbol graph only has one symbol in the overload
5001+
// group, whereas the iOS graph has two. Due to the way that Symbol loaded the overload
5002+
// mixins, `symbol-1` wouldn't save its overload data, which would trip an assertion in
5003+
// DocumentationContext. We need to ensure that an overload group is properly created, and
5004+
// that both symbols are correctly grouped underneath it.
5005+
let tempURL = try createTempFolder(content: [
5006+
Folder(name: "unit-test.docc", content: [
5007+
JSONFile(name: "ModuleName-macos.symbols.json", content: makeSymbolGraph(
5008+
moduleName: "ModuleName",
5009+
platform: .init(operatingSystem: .init(name: "macosx")),
5010+
symbols: [
5011+
makeSymbol(identifier: "symbol-1", kind: symbolKind),
5012+
])),
5013+
JSONFile(name: "ModuleName-ios.symbols.json", content: makeSymbolGraph(
5014+
moduleName: "ModuleName",
5015+
platform: .init(operatingSystem: .init(name: "ios")),
5016+
symbols: [
5017+
makeSymbol(identifier: "symbol-1", kind: symbolKind),
5018+
makeSymbol(identifier: "symbol-2", kind: symbolKind),
5019+
])),
5020+
])
5021+
])
5022+
let (_, bundle, context) = try loadBundle(from: tempURL)
5023+
let moduleReference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/ModuleName", sourceLanguage: .swift)
5024+
5025+
let overloadGroupNode: DocumentationNode
5026+
let overloadGroupSymbol: Symbol
5027+
let overloadGroupReferences: Symbol.Overloads
5028+
5029+
// Even though the macOS symbol graph doesn't contain an overload group, one should still
5030+
// have been created from the iOS symbol graph, and that overload group should reference
5031+
// both symbols.
5032+
switch context.resolve(.unresolved(.init(topicURL: .init(symbolPath: "SymbolName"))), in: moduleReference, fromSymbolLink: true) {
5033+
case let .failure(_, errorMessage):
5034+
XCTFail("Could not resolve overload group page. Error message: \(errorMessage)")
5035+
return
5036+
case let .success(overloadGroupReference):
5037+
overloadGroupNode = try context.entity(with: overloadGroupReference)
5038+
overloadGroupSymbol = try XCTUnwrap(overloadGroupNode.semantic as? Symbol)
5039+
overloadGroupReferences = try XCTUnwrap(overloadGroupSymbol.overloadsVariants.firstValue)
5040+
5041+
XCTAssertEqual(overloadGroupReferences.displayIndex, 0)
5042+
5043+
let unifiedSymbol = try XCTUnwrap(overloadGroupNode.unifiedSymbol)
5044+
XCTAssertEqual(unifiedSymbol.uniqueIdentifier, "symbol-1" + SymbolGraph.Symbol.overloadGroupIdentifierSuffix)
5045+
}
5046+
5047+
let overloadedReferences = try ["symbol-1", "symbol-2"]
5048+
.map { try XCTUnwrap(context.documentationCache.reference(symbolID: $0)) }
5049+
5050+
for (index, reference) in overloadedReferences.indexed() {
5051+
let overloadedDocumentationNode = try XCTUnwrap(context.documentationCache[reference])
5052+
let overloadedSymbol = try XCTUnwrap(overloadedDocumentationNode.semantic as? Symbol)
5053+
5054+
let overloads = try XCTUnwrap(overloadedSymbol.overloadsVariants.firstValue)
5055+
5056+
// Make sure that each symbol contains all of its sibling overloads.
5057+
XCTAssertEqual(overloads.references.count, overloadedReferences.count - 1)
5058+
for (otherIndex, otherReference) in overloadedReferences.indexed() where otherIndex != index {
5059+
XCTAssert(overloads.references.contains(otherReference))
5060+
}
5061+
5062+
if overloads.displayIndex == 0 {
5063+
// The first declaration in the display list should be the same declaration as
5064+
// the overload group page
5065+
XCTAssertEqual(overloadedSymbol.declaration.first?.value.declarationFragments, overloadGroupSymbol.declaration.first?.value.declarationFragments)
5066+
} else {
5067+
// Otherwise, this reference should also be referenced by the overload group
5068+
XCTAssert(overloadGroupReferences.references.contains(reference))
5069+
}
5070+
}
5071+
}
5072+
5073+
/// Ensure that overload groups are correctly loaded into the path hierarchy and create nodes,
5074+
/// even when they came from an extension symbol graph.
5075+
func testContextGeneratesOverloadGroupsForExtensionGraphOverloads() throws {
5076+
enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled)
5077+
5078+
let symbolKind = try XCTUnwrap(SymbolGraph.Symbol.KindIdentifier.allCases.filter({ $0.isOverloadableKind }).first)
5079+
5080+
let tempURL = try createTempFolder(content: [
5081+
Folder(name: "unit-test.docc", content: [
5082+
JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(
5083+
moduleName: "ModuleName",
5084+
platform: .init(operatingSystem: .init(name: "macosx")),
5085+
symbols: [
5086+
makeSymbol(name: "RegularSymbol", identifier: "RegularSymbol", kind: .class),
5087+
])),
5088+
JSONFile(name: "[email protected]", content: makeSymbolGraph(
5089+
moduleName: "OtherModule",
5090+
platform: .init(operatingSystem: .init(name: "macosx")),
5091+
symbols: [
5092+
makeSymbol(identifier: "symbol-1", kind: symbolKind),
5093+
makeSymbol(identifier: "symbol-2", kind: symbolKind),
5094+
])),
5095+
])
5096+
])
5097+
let (_, bundle, context) = try loadBundle(from: tempURL)
5098+
let moduleReference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/ModuleName", sourceLanguage: .swift)
5099+
5100+
let overloadGroupNode: DocumentationNode
5101+
let overloadGroupSymbol: Symbol
5102+
let overloadGroupReferences: Symbol.Overloads
5103+
5104+
switch context.resolve(.unresolved(.init(topicURL: .init(symbolPath: "SymbolName"))), in: moduleReference, fromSymbolLink: true) {
5105+
case let .failure(_, errorMessage):
5106+
XCTFail("Could not resolve overload group page. Error message: \(errorMessage)")
5107+
return
5108+
case let .success(overloadGroupReference):
5109+
overloadGroupNode = try context.entity(with: overloadGroupReference)
5110+
overloadGroupSymbol = try XCTUnwrap(overloadGroupNode.semantic as? Symbol)
5111+
overloadGroupReferences = try XCTUnwrap(overloadGroupSymbol.overloadsVariants.firstValue)
5112+
5113+
XCTAssertEqual(overloadGroupReferences.displayIndex, 0)
5114+
5115+
let unifiedSymbol = try XCTUnwrap(overloadGroupNode.unifiedSymbol)
5116+
XCTAssertEqual(unifiedSymbol.uniqueIdentifier, "symbol-1" + SymbolGraph.Symbol.overloadGroupIdentifierSuffix)
5117+
}
5118+
5119+
let overloadedReferences = try ["symbol-1", "symbol-2"]
5120+
.map { try XCTUnwrap(context.documentationCache.reference(symbolID: $0)) }
5121+
5122+
for (index, reference) in overloadedReferences.indexed() {
5123+
let overloadedDocumentationNode = try XCTUnwrap(context.documentationCache[reference])
5124+
let overloadedSymbol = try XCTUnwrap(overloadedDocumentationNode.semantic as? Symbol)
5125+
5126+
let overloads = try XCTUnwrap(overloadedSymbol.overloadsVariants.firstValue)
5127+
5128+
// Make sure that each symbol contains all of its sibling overloads.
5129+
XCTAssertEqual(overloads.references.count, overloadedReferences.count - 1)
5130+
for (otherIndex, otherReference) in overloadedReferences.indexed() where otherIndex != index {
5131+
XCTAssert(overloads.references.contains(otherReference))
5132+
}
5133+
5134+
if overloads.displayIndex == 0 {
5135+
// The first declaration in the display list should be the same declaration as
5136+
// the overload group page
5137+
XCTAssertEqual(overloadedSymbol.declaration.first?.value.declarationFragments, overloadGroupSymbol.declaration.first?.value.declarationFragments)
5138+
} else {
5139+
// Otherwise, this reference should also be referenced by the overload group
5140+
XCTAssert(overloadGroupReferences.references.contains(reference))
5141+
}
5142+
}
5143+
}
5144+
49225145
// A test helper that creates a symbol with a given identifier and kind.
49235146
private func makeSymbol(
49245147
name: String = "SymbolName",

0 commit comments

Comments
 (0)