Skip to content

Commit 57d09b3

Browse files
load cross-import overlay module data, even for extensions (#332)
rdar://94736726
1 parent ed1770f commit 57d09b3

File tree

11 files changed

+284
-21
lines changed

11 files changed

+284
-21
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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -986,7 +986,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
986986

987987
/// Creates a topic graph node and a documentation node for the given symbol.
988988
private func preparedSymbolData(_ symbol: UnifiedSymbolGraph.Symbol, reference: ResolvedTopicReference, module: SymbolGraph.Module, moduleReference: ResolvedTopicReference, bundle: DocumentationBundle, fileURL symbolGraphURL: URL?) -> AddSymbolResultWithProblems {
989-
let documentation = DocumentationNode(reference: reference, unifiedSymbol: symbol, platformName: module.platform.name, moduleReference: moduleReference, bystanderModules: module.bystanders)
989+
let documentation = DocumentationNode(reference: reference, unifiedSymbol: symbol, moduleData: module, moduleReference: moduleReference)
990990
let source: TopicGraph.Node.ContentLocation // TODO: use a list of URLs for the files in a unified graph
991991
if let symbolGraphURL = symbolGraphURL {
992992
source = .file(url: symbolGraphURL)

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ struct SymbolGraphLoader {
139139
var symbolGraph = symbolGraphs[url]!
140140
let (moduleName, isMainSymbolGraph) = Self.moduleNameFor(symbolGraph, at: url)
141141

142-
if !isMainSymbolGraph {
142+
if !isMainSymbolGraph && symbolGraph.module.bystanders == nil {
143143
// If this is an extending another module, change the module name to match the exteneded module.
144144
// This makes the symbols in this graph have a path that starts with the extended module's name.
145145
symbolGraph.module.name = moduleName
@@ -250,8 +250,12 @@ struct SymbolGraphLoader {
250250
let isMainSymbolGraph = !url.lastPathComponent.contains("@")
251251

252252
let moduleName: String
253-
if isMainSymbolGraph {
253+
if isMainSymbolGraph || symbolGraph.module.bystanders != nil {
254254
// For main symbol graphs, get the module name from the symbol graph's data
255+
256+
// When bystander modules are present, the symbol graph is a cross-import overlay, and
257+
// we need to preserve the original module name to properly render it. It is still
258+
// kept with the extension symbols, due to the merging behavior of UnifiedSymbolGraph.
255259
moduleName = symbolGraph.module.name
256260
} else {
257261
// For extension symbol graphs, derive the extended module's name from the file name.

Sources/SwiftDocC/Model/DocumentationNode.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public struct DocumentationNode {
159159
/// - symbol: The symbol to create a documentation node for.
160160
/// - platformName: The name of the platforms for which the node is available.
161161
/// - moduleName: The name of the module that the symbol belongs to.
162-
init(reference: ResolvedTopicReference, unifiedSymbol: UnifiedSymbolGraph.Symbol, platformName: String?, moduleReference: ResolvedTopicReference, bystanderModules: [String]? = nil) {
162+
init(reference: ResolvedTopicReference, unifiedSymbol: UnifiedSymbolGraph.Symbol, moduleData: SymbolGraph.Module, moduleReference: ResolvedTopicReference) {
163163
self.reference = reference
164164

165165
guard let defaultSymbol = unifiedSymbol.defaultSymbol else {
@@ -175,6 +175,8 @@ public struct DocumentationNode {
175175
self.markup = Document()
176176
self.docChunks = []
177177
self.tags = (returns: [], throws: [], parameters: [])
178+
179+
let platformName = moduleData.platform.name
178180

179181
let symbolAvailabilityVariants = DocumentationDataVariants(
180182
symbolData: unifiedSymbol.mixins,
@@ -259,7 +261,7 @@ public struct DocumentationNode {
259261
returnsSectionVariants: .empty,
260262
parametersSectionVariants: .empty,
261263
redirectsVariants: .empty,
262-
bystanderModuleNames: bystanderModules
264+
crossImportOverlayModule: moduleData.bystanders.map({ (moduleData.name, $0) })
263265
)
264266

265267
try! semanticSymbol.mergeDeclarations(unifiedSymbol: unifiedSymbol)
@@ -468,9 +470,9 @@ public struct DocumentationNode {
468470
}
469471

470472
@available(*, deprecated, message: "Use init(reference:symbol:platformName:moduleReference:article:engine:bystanderModules:) instead")
471-
public init(reference: ResolvedTopicReference, symbol: SymbolGraph.Symbol, platformName: String?, moduleName: String, article: Article?, engine: DiagnosticEngine, bystanderModules: [String]? = nil) {
473+
public init(reference: ResolvedTopicReference, symbol: SymbolGraph.Symbol, platformName: String?, moduleName: String, article: Article?, engine: DiagnosticEngine) {
472474
let assumedModuleReference = ResolvedTopicReference(bundleIdentifier: reference.bundleIdentifier, path: reference.pathComponents.prefix(2).joined(separator: "/"), sourceLanguage: reference.sourceLanguage)
473-
self.init(reference: reference, symbol: symbol, platformName: platformName, moduleReference: assumedModuleReference, article: article, engine: engine, bystanderModules: bystanderModules)
475+
self.init(reference: reference, symbol: symbol, platformName: platformName, moduleReference: assumedModuleReference, article: article, engine: engine)
474476
}
475477

476478
/// Initializes a documentation node to represent a symbol from a symbol graph.
@@ -483,7 +485,7 @@ public struct DocumentationNode {
483485
/// - article: The documentation extension content for this symbol.
484486
/// - engine:The engine that collects any problems encountered during initialization.
485487
/// - bystanderModules: An optional list of cross-import module names.
486-
public init(reference: ResolvedTopicReference, symbol: SymbolGraph.Symbol, platformName: String?, moduleReference: ResolvedTopicReference, article: Article?, engine: DiagnosticEngine, bystanderModules: [String]? = nil) {
488+
public init(reference: ResolvedTopicReference, symbol: SymbolGraph.Symbol, platformName: String?, moduleReference: ResolvedTopicReference, article: Article?, engine: DiagnosticEngine) {
487489
self.reference = reference
488490

489491
guard reference.sourceLanguage == .swift else {
@@ -559,8 +561,7 @@ public struct DocumentationNode {
559561
seeAlsoVariants: .init(swiftVariant: markupModel.seeAlsoSection),
560562
returnsSectionVariants: .init(swiftVariant: markupModel.discussionTags.flatMap({ $0.returns.isEmpty ? nil : ReturnsSection(content: $0.returns[0].contents) })),
561563
parametersSectionVariants: .init(swiftVariant: markupModel.discussionTags.flatMap({ $0.parameters.isEmpty ? nil : ParametersSection(parameters: $0.parameters) })),
562-
redirectsVariants: .init(swiftVariant: article?.redirects),
563-
bystanderModuleNames: bystanderModules
564+
redirectsVariants: .init(swiftVariant: article?.redirects)
564565
)
565566

566567
updateAnchorSections()

Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,10 +1057,13 @@ public struct RenderNodeTranslator: SemanticVisitor {
10571057
}
10581058

10591059
let moduleName = context.moduleName(forModuleReference: symbol.moduleReference)
1060-
1061-
node.metadata.modulesVariants = VariantCollection(defaultValue:
1062-
[RenderMetadata.Module(name: moduleName.displayName, relatedModules: symbol.bystanderModuleNames)]
1063-
)
1060+
1061+
if let crossImportOverlayModule = symbol.crossImportOverlayModule {
1062+
node.metadata.modulesVariants = VariantCollection(defaultValue: [RenderMetadata.Module(name: crossImportOverlayModule.declaringModule, relatedModules: crossImportOverlayModule.bystanderModules)])
1063+
} else {
1064+
node.metadata.modulesVariants = VariantCollection(defaultValue: [RenderMetadata.Module(name: moduleName.displayName, relatedModules: nil)]
1065+
)
1066+
}
10641067

10651068
node.metadata.extendedModuleVariants = VariantCollection<String?>(defaultValue: symbol.extendedModule)
10661069

Sources/SwiftDocC/Semantics/ReferenceResolver.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ struct ReferenceResolver: SemanticVisitor {
423423
returnsSectionVariants: newReturnsVariants,
424424
parametersSectionVariants: newParametersVariants,
425425
redirectsVariants: symbol.redirectsVariants,
426-
bystanderModuleNames: symbol.bystanderModuleNames,
426+
crossImportOverlayModule: symbol.crossImportOverlayModule,
427427
originVariants: symbol.originVariants,
428428
automaticTaskGroupsVariants: symbol.automaticTaskGroupsVariants
429429
)

Sources/SwiftDocC/Semantics/Symbol/Symbol.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,15 @@ public final class Symbol: Semantic, Abstracted, Redirected, AutomaticTaskGroups
113113

114114
/// The name of the module extension in which the symbol is defined, if applicable.
115115
internal(set) public var extendedModule: String?
116+
117+
/// The names of any "bystander" modules required for this symbol, if it came from a cross-import overlay.
118+
@available(*, deprecated, message: "Use crossImportOverlayModule instead")
119+
public var bystanderModuleNames: [String]? {
120+
self.crossImportOverlayModule?.bystanderModules
121+
}
116122

117123
/// Optional cross-import module names of the symbol.
118-
internal(set) public var bystanderModuleNames: [String]?
124+
internal(set) public var crossImportOverlayModule: (declaringModule: String, bystanderModules: [String])?
119125

120126
/// Whether the symbol is required in its context, in each language variant the symbol is available in.
121127
public var isRequiredVariants: DocumentationDataVariants<Bool>
@@ -227,7 +233,7 @@ public final class Symbol: Semantic, Abstracted, Redirected, AutomaticTaskGroups
227233
returnsSectionVariants: DocumentationDataVariants<ReturnsSection>,
228234
parametersSectionVariants: DocumentationDataVariants<ParametersSection>,
229235
redirectsVariants: DocumentationDataVariants<[Redirect]>,
230-
bystanderModuleNames: [String]? = nil,
236+
crossImportOverlayModule: (declaringModule: String, bystanderModules: [String])? = nil,
231237
originVariants: DocumentationDataVariants<SymbolGraph.Relationship.SourceOrigin> = .init(),
232238
automaticTaskGroupsVariants: DocumentationDataVariants<[AutomaticTaskGroupSection]> = .init(defaultVariantValue: [])
233239
) {
@@ -238,7 +244,7 @@ public final class Symbol: Semantic, Abstracted, Redirected, AutomaticTaskGroups
238244
self.roleHeadingVariants = roleHeadingVariants
239245
self.platformNameVariants = platformNameVariants
240246
self.moduleReference = moduleReference
241-
self.bystanderModuleNames = bystanderModuleNames
247+
self.crossImportOverlayModule = crossImportOverlayModule
242248
self.isRequiredVariants = requiredVariants
243249
self.externalIDVariants = externalIDVariants
244250
self.accessLevelVariants = accessLevelVariants

Tests/SwiftDocCTests/Rendering/RenderMetadataTests.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,41 @@ class RenderMetadataTests: XCTestCase {
107107
let symbol = try XCTUnwrap(entity.semantic as? Symbol)
108108

109109
// Verify it contains the bystanders data
110-
XCTAssertEqual(symbol.bystanderModuleNames, ["Foundation"])
110+
XCTAssertEqual(symbol.crossImportOverlayModule?.bystanderModules, ["Foundation"])
111111

112112
// Verify the rendered metadata contains the bystanders
113113
let converter = DocumentationNodeConverter(bundle: bundle, context: context)
114114
let renderNode = try converter.convert(entity, at: nil)
115115
XCTAssertEqual(renderNode.metadata.modules?.first?.name, "MyKit")
116116
XCTAssertEqual(renderNode.metadata.modules?.first?.relatedModules, ["Foundation"])
117117
}
118+
119+
/// Test that when a bystanders symbol graph is loaded that extends a different module, that
120+
/// those symbols correctly report the modules when rendered.
121+
func testRendersBystanderExtensionsFromSymbolGraph() throws {
122+
let (_, bundle, context) = try testBundleAndContext(copying: "TestBundle", externalResolvers: [:]) { url in
123+
let baseSymbolGraphURL = Bundle.module.url(
124+
forResource: "BaseKit.symbols", withExtension: "json", subdirectory: "Test Resources")!
125+
try FileManager.default.copyItem(at: baseSymbolGraphURL, to: url.appendingPathComponent("BaseKit.symbols.json"))
126+
let overlaySymbolGraphURL = Bundle.module.url(
127+
forResource: "[email protected]", withExtension: "json", subdirectory: "Test Resources")!
128+
try FileManager.default.copyItem(at: overlaySymbolGraphURL, to: url.appendingPathComponent("[email protected]"))
129+
}
130+
131+
// Verify the symbol from bystanders graph is present in the documentation context.
132+
let reference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/BaseKit/OtherStruct/someFunc()", sourceLanguage: .swift)
133+
let entity = try XCTUnwrap(try? context.entity(with: reference))
134+
let symbol = try XCTUnwrap(entity.semantic as? Symbol)
135+
136+
// Verify it contains the bystanders data
137+
XCTAssertEqual(symbol.crossImportOverlayModule?.bystanderModules, ["BaseKit"])
138+
139+
// Verify the rendered metadata contains the bystanders
140+
let converter = DocumentationNodeConverter(bundle: bundle, context: context)
141+
let renderNode = try converter.convert(entity, at: nil)
142+
XCTAssertEqual(renderNode.metadata.modules?.first?.name, "OverlayTest")
143+
XCTAssertEqual(renderNode.metadata.modules?.first?.relatedModules, ["BaseKit"])
144+
}
118145

119146
func testEmitsTitleVariantsDuringEncoding() throws {
120147
var metadata = RenderMetadata()

Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorSymbolVariantsTests.swift

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,35 @@ class RenderNodeTranslatorSymbolVariantsTests: XCTestCase {
5656
context.preResolveModuleNames()
5757
},
5858
configureSymbol: { symbol in
59-
symbol.bystanderModuleNames = ["Custom Bystander Title"]
59+
symbol.crossImportOverlayModule = ("Custom Module Title", ["Custom Bystander Title"])
60+
},
61+
assertOriginalRenderNode: { renderNode in
62+
try assertModule(
63+
renderNode.metadata.modules,
64+
expectedName: "Custom Module Title",
65+
expectedRelatedModules: ["Custom Bystander Title"]
66+
)
67+
},
68+
assertAfterApplyingVariant: { renderNode in
69+
try assertModule(
70+
renderNode.metadata.modules,
71+
expectedName: "Custom Module Title",
72+
expectedRelatedModules: ["Custom Bystander Title"]
73+
)
74+
}
75+
)
76+
}
77+
78+
/// Make sure that when a symbol has `crossImportOverlayModule` information, that module name is used instead of its `moduleReference`.
79+
func testMultipleModulesWithDifferentBystanderModule() throws {
80+
try assertMultiVariantSymbol(
81+
configureContext: { context, resolvedTopicReference in
82+
let moduleReference = ResolvedTopicReference(bundleIdentifier: resolvedTopicReference.bundleIdentifier, path: "/documentation/MyKit", sourceLanguage: .swift)
83+
context.documentationCache[moduleReference]?.name = .conceptual(title: "Extended Module Title")
84+
context.preResolveModuleNames()
85+
},
86+
configureSymbol: { symbol in
87+
symbol.crossImportOverlayModule = ("Custom Module Title", ["Custom Bystander Title"])
6088
},
6189
assertOriginalRenderNode: { renderNode in
6290
try assertModule(
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
{
2+
"metadata": {
3+
"formatVersion": {
4+
"major": 0,
5+
"minor": 5,
6+
"patch": 3
7+
},
8+
"generator": "app/1.0"
9+
},
10+
"module": {
11+
"name": "BaseKit",
12+
"platform": {
13+
"architecture": "x86_64",
14+
"vendor": "apple",
15+
"operatingSystem": {
16+
"name": "macosx",
17+
"minimumVersion": {
18+
"major": 12,
19+
"minor": 3,
20+
"patch": 0
21+
}
22+
}
23+
}
24+
},
25+
"symbols": [
26+
{
27+
"kind": {
28+
"identifier": "swift.struct",
29+
"displayName": "Structure"
30+
},
31+
"identifier": {
32+
"precise": "s:7BaseKit11OtherStructV",
33+
"interfaceLanguage": "swift"
34+
},
35+
"pathComponents": [
36+
"OtherStruct"
37+
],
38+
"names": {
39+
"title": "OtherStruct",
40+
"navigator": [
41+
{
42+
"kind": "identifier",
43+
"spelling": "OtherStruct"
44+
}
45+
],
46+
"subHeading": [
47+
{
48+
"kind": "keyword",
49+
"spelling": "struct"
50+
},
51+
{
52+
"kind": "text",
53+
"spelling": " "
54+
},
55+
{
56+
"kind": "identifier",
57+
"spelling": "OtherStruct"
58+
}
59+
]
60+
},
61+
"declarationFragments": [
62+
{
63+
"kind": "keyword",
64+
"spelling": "struct"
65+
},
66+
{
67+
"kind": "text",
68+
"spelling": " "
69+
},
70+
{
71+
"kind": "identifier",
72+
"spelling": "OtherStruct"
73+
}
74+
],
75+
"accessLevel": "public",
76+
"location": {
77+
"uri": "file:///path/to/OverlayTest/BaseKit/BaseKit.swift",
78+
"position": {
79+
"line": 9,
80+
"character": 14
81+
}
82+
}
83+
}
84+
],
85+
"relationships": []
86+
}

0 commit comments

Comments
 (0)