Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 10 additions & 9 deletions Sources/SwiftDocC/Infrastructure/DocumentationContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1161,8 +1161,6 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {

// For inherited symbols we remove the source docs (if inheriting docs is disabled) before creating their documentation nodes.
for (_, relationships) in unifiedSymbolGraph.relationshipsByLanguage {
var overloadGroups = [String: [String]]()

for relationship in relationships {
// Check for an origin key.
if let sourceOrigin = relationship[mixin: SymbolGraph.Relationship.SourceOrigin.self],
Expand All @@ -1176,15 +1174,18 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
localCache: documentationCache,
moduleName: moduleName
)
} else if relationship.kind == .overloadOf {
// An 'overloadOf' relationship points from symbol -> group
overloadGroups[relationship.target, default: []].append(relationship.source)
}
}

addOverloadGroupReferences(overloadGroups: overloadGroups)
}


let overloadGroups: [String: Set<String>] =
unifiedSymbolGraph.relationshipsByLanguage.values.flatMap({
$0.filter { $0.kind == .overloadOf }
}).reduce(into: [:], { acc, relationship in
acc[relationship.target, default: []].insert(relationship.source)
})
addOverloadGroupReferences(overloadGroups: overloadGroups)

if let rootURL = symbolGraphLoader.mainModuleURL(forModule: moduleName), let rootModule = unifiedSymbolGraph.moduleData[rootURL] {
addPreparedSymbolToContext(
preparedSymbolData(.init(fromSingleSymbol: moduleSymbol, module: rootModule, isMainGraph: true), reference: moduleReference, module: rootModule, moduleReference: moduleReference, fileURL: fileURL)
Expand Down Expand Up @@ -2395,7 +2396,7 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
return automaticallyCuratedSymbols
}

private func addOverloadGroupReferences(overloadGroups: [String: [String]]) {
private func addOverloadGroupReferences(overloadGroups: [String: Set<String>]) {
guard FeatureFlags.current.isExperimentalOverloadedSymbolPresentationEnabled else {
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,17 @@ struct PathHierarchy {
existingNode.languages.insert(language!) // If we have symbols in this graph we have a language as well
} else {
assert(!symbol.pathComponents.isEmpty, "A symbol should have at least its own name in its path components.")

if symbol.identifier.precise.hasSuffix(SymbolGraph.Symbol.overloadGroupIdentifierSuffix),
loader.unifiedGraphs[moduleNode.name]?.symbols.keys.contains(symbol.identifier.precise) != true {
// Overload groups can be discarded in the unified symbol graph collector if
// they don't reflect the default overload across all platforms. In this
// case, we don't want to add these nodes to the path hierarchy since
// they've been discarded from the unified graph that's used to generate
// documentation nodes.
continue
}

let node = Node(symbol: symbol, name: symbol.pathComponents.last!)
// Disfavor synthesized symbols when they collide with other symbol with the same path.
// FIXME: Get information about synthesized symbols from SymbolKit https://github.com/apple/swift-docc-symbolkit/issues/58
Expand Down
11 changes: 3 additions & 8 deletions Sources/SwiftDocC/Model/DocumentationNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,9 @@ public struct DocumentationNode {
}

let overloadVariants = DocumentationDataVariants(
symbolData: unifiedSymbol.mixins,
platformName: platformName
) { mixins -> Symbol.Overloads? in
guard let overloadData = mixins[SymbolGraph.Symbol.OverloadData.mixinKey] as? SymbolGraph.Symbol.OverloadData else {
return nil
}
return .init(references: [], displayIndex: overloadData.overloadGroupIndex)
}
swiftVariant: unifiedSymbol.unifiedOverloadData.map { overloadData in
Symbol.Overloads(references: [], displayIndex: overloadData.overloadGroupIndex)
})

var languages = Set([reference.sourceLanguage])
var operatingSystemName = platformName.map({ Set([$0]) }) ?? []
Expand Down
9 changes: 7 additions & 2 deletions Sources/SwiftDocCTestUtilities/SymbolGraphCreation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,20 @@ import XCTest
import SymbolKit

extension XCTestCase {
public func makeSymbolGraph(moduleName: String, symbols: [SymbolGraph.Symbol] = [], relationships: [SymbolGraph.Relationship] = []) -> SymbolGraph {
public func makeSymbolGraph(
moduleName: String,
platform: SymbolGraph.Platform = .init(),
symbols: [SymbolGraph.Symbol] = [],
relationships: [SymbolGraph.Relationship] = []
) -> SymbolGraph {
return SymbolGraph(
metadata: SymbolGraph.Metadata(
formatVersion: SymbolGraph.SemanticVersion(major: 0, minor: 6, patch: 0),
generator: "unit-test"
),
module: SymbolGraph.Module(
name: moduleName,
platform: SymbolGraph.Platform(architecture: nil, vendor: nil, operatingSystem: nil)
platform: platform
),
symbols: symbols,
relationships: relationships
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4919,6 +4919,229 @@ let expected = """
"Unknown feature flag in Info.plist: 'ExperimenalOverloadedSymbolPresentation'. Possible suggestions: 'ExperimentalOverloadedSymbolPresentation'")
}

func testContextGeneratesUnifiedOverloadGroupsAcrossPlatforms() throws {
enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled)

let symbolKind = try XCTUnwrap(SymbolGraph.Symbol.KindIdentifier.allCases.filter({ $0.isOverloadableKind }).first)

let tempURL = try createTempFolder(content: [
Folder(name: "unit-test.docc", content: [
JSONFile(name: "ModuleName-macos.symbols.json", content: makeSymbolGraph(
moduleName: "ModuleName",
platform: .init(operatingSystem: .init(name: "macosx")),
symbols: [
makeSymbol(identifier: "symbol-1", kind: symbolKind),
makeSymbol(identifier: "symbol-2", kind: symbolKind),
])),
JSONFile(name: "ModuleName-ios.symbols.json", content: makeSymbolGraph(
moduleName: "ModuleName",
platform: .init(operatingSystem: .init(name: "ios")),
symbols: [
makeSymbol(identifier: "symbol-2", kind: symbolKind),
makeSymbol(identifier: "symbol-3", kind: symbolKind),
])),
])
])
let (_, bundle, context) = try loadBundle(from: tempURL)
let moduleReference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/ModuleName", sourceLanguage: .swift)

let overloadGroupNode: DocumentationNode
let overloadGroupSymbol: Symbol
let overloadGroupReferences: Symbol.Overloads

// There should only be one overload group for `SymbolName` - the one from the iOS symbol
// graph should have been removed during graph collection.
switch context.resolve(.unresolved(.init(topicURL: .init(symbolPath: "SymbolName"))), in: moduleReference, fromSymbolLink: true) {
case let .failure(_, errorMessage):
XCTFail("Could not resolve overload group page. Error message: \(errorMessage)")
return
case let .success(overloadGroupReference):
overloadGroupNode = try context.entity(with: overloadGroupReference)
overloadGroupSymbol = try XCTUnwrap(overloadGroupNode.semantic as? Symbol)
overloadGroupReferences = try XCTUnwrap(overloadGroupSymbol.overloadsVariants.firstValue)

XCTAssertEqual(overloadGroupReferences.displayIndex, 0)

let unifiedSymbol = try XCTUnwrap(overloadGroupNode.unifiedSymbol)
XCTAssertEqual(unifiedSymbol.uniqueIdentifier, "symbol-1" + SymbolGraph.Symbol.overloadGroupIdentifierSuffix)
}

let overloadedReferences = try ["symbol-1", "symbol-2", "symbol-3"]
.map { try XCTUnwrap(context.documentationCache.reference(symbolID: $0)) }

for (index, reference) in overloadedReferences.indexed() {
let overloadedDocumentationNode = try XCTUnwrap(context.documentationCache[reference])
let overloadedSymbol = try XCTUnwrap(overloadedDocumentationNode.semantic as? Symbol)

let overloads = try XCTUnwrap(overloadedSymbol.overloadsVariants.firstValue)

// Make sure that each symbol contains all of its sibling overloads.
XCTAssertEqual(overloads.references.count, overloadedReferences.count - 1)
for (otherIndex, otherReference) in overloadedReferences.indexed() where otherIndex != index {
XCTAssert(overloads.references.contains(otherReference))
}

if overloads.displayIndex == 0 {
// The first declaration in the display list should be the same declaration as
// the overload group page
XCTAssertEqual(overloadedSymbol.declaration.first?.value.declarationFragments, overloadGroupSymbol.declaration.first?.value.declarationFragments)
} else {
// Otherwise, this reference should also be referenced by the overload group
XCTAssert(overloadGroupReferences.references.contains(reference))
}
}
}

func testContextGeneratesOverloadGroupsWhenOnePlatformHasNoOverloads() throws {
enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled)

let symbolKind = try XCTUnwrap(SymbolGraph.Symbol.KindIdentifier.allCases.filter({ $0.isOverloadableKind }).first)

// This situation used to crash. The macOS symbol graph only has one symbol in the overload
// group, whereas the iOS graph has two. Due to the way that Symbol loaded the overload
// mixins, `symbol-1` wouldn't save its overload data, which would trip an assertion in
// DocumentationContext. We need to ensure that an overload group is properly created, and
// that both symbols are correctly grouped underneath it.
let tempURL = try createTempFolder(content: [
Folder(name: "unit-test.docc", content: [
JSONFile(name: "ModuleName-macos.symbols.json", content: makeSymbolGraph(
moduleName: "ModuleName",
platform: .init(operatingSystem: .init(name: "macosx")),
symbols: [
makeSymbol(identifier: "symbol-1", kind: symbolKind),
])),
JSONFile(name: "ModuleName-ios.symbols.json", content: makeSymbolGraph(
moduleName: "ModuleName",
platform: .init(operatingSystem: .init(name: "ios")),
symbols: [
makeSymbol(identifier: "symbol-1", kind: symbolKind),
makeSymbol(identifier: "symbol-2", kind: symbolKind),
])),
])
])
let (_, bundle, context) = try loadBundle(from: tempURL)
let moduleReference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/ModuleName", sourceLanguage: .swift)

let overloadGroupNode: DocumentationNode
let overloadGroupSymbol: Symbol
let overloadGroupReferences: Symbol.Overloads

// Even though the macOS symbol graph doesn't contain an overload group, one should still
// have been created from the iOS symbol graph, and that overload group should reference
// both symbols.
switch context.resolve(.unresolved(.init(topicURL: .init(symbolPath: "SymbolName"))), in: moduleReference, fromSymbolLink: true) {
case let .failure(_, errorMessage):
XCTFail("Could not resolve overload group page. Error message: \(errorMessage)")
return
case let .success(overloadGroupReference):
overloadGroupNode = try context.entity(with: overloadGroupReference)
overloadGroupSymbol = try XCTUnwrap(overloadGroupNode.semantic as? Symbol)
overloadGroupReferences = try XCTUnwrap(overloadGroupSymbol.overloadsVariants.firstValue)

XCTAssertEqual(overloadGroupReferences.displayIndex, 0)

let unifiedSymbol = try XCTUnwrap(overloadGroupNode.unifiedSymbol)
XCTAssertEqual(unifiedSymbol.uniqueIdentifier, "symbol-1" + SymbolGraph.Symbol.overloadGroupIdentifierSuffix)
}

let overloadedReferences = try ["symbol-1", "symbol-2"]
.map { try XCTUnwrap(context.documentationCache.reference(symbolID: $0)) }

for (index, reference) in overloadedReferences.indexed() {
let overloadedDocumentationNode = try XCTUnwrap(context.documentationCache[reference])
let overloadedSymbol = try XCTUnwrap(overloadedDocumentationNode.semantic as? Symbol)

let overloads = try XCTUnwrap(overloadedSymbol.overloadsVariants.firstValue)

// Make sure that each symbol contains all of its sibling overloads.
XCTAssertEqual(overloads.references.count, overloadedReferences.count - 1)
for (otherIndex, otherReference) in overloadedReferences.indexed() where otherIndex != index {
XCTAssert(overloads.references.contains(otherReference))
}

if overloads.displayIndex == 0 {
// The first declaration in the display list should be the same declaration as
// the overload group page
XCTAssertEqual(overloadedSymbol.declaration.first?.value.declarationFragments, overloadGroupSymbol.declaration.first?.value.declarationFragments)
} else {
// Otherwise, this reference should also be referenced by the overload group
XCTAssert(overloadGroupReferences.references.contains(reference))
}
}
}

/// Ensure that overload groups are correctly loaded into the path hierarchy and create nodes,
/// even when they came from an extension symbol graph.
func testContextGeneratesOverloadGroupsForExtensionGraphOverloads() throws {
enableFeatureFlag(\.isExperimentalOverloadedSymbolPresentationEnabled)

let symbolKind = try XCTUnwrap(SymbolGraph.Symbol.KindIdentifier.allCases.filter({ $0.isOverloadableKind }).first)

let tempURL = try createTempFolder(content: [
Folder(name: "unit-test.docc", content: [
JSONFile(name: "ModuleName.symbols.json", content: makeSymbolGraph(
moduleName: "ModuleName",
platform: .init(operatingSystem: .init(name: "macosx")),
symbols: [
makeSymbol(name: "RegularSymbol", identifier: "RegularSymbol", kind: .class),
])),
JSONFile(name: "[email protected]", content: makeSymbolGraph(
moduleName: "OtherModule",
platform: .init(operatingSystem: .init(name: "macosx")),
symbols: [
makeSymbol(identifier: "symbol-1", kind: symbolKind),
makeSymbol(identifier: "symbol-2", kind: symbolKind),
])),
])
])
let (_, bundle, context) = try loadBundle(from: tempURL)
let moduleReference = ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/ModuleName", sourceLanguage: .swift)

let overloadGroupNode: DocumentationNode
let overloadGroupSymbol: Symbol
let overloadGroupReferences: Symbol.Overloads

switch context.resolve(.unresolved(.init(topicURL: .init(symbolPath: "SymbolName"))), in: moduleReference, fromSymbolLink: true) {
case let .failure(_, errorMessage):
XCTFail("Could not resolve overload group page. Error message: \(errorMessage)")
return
case let .success(overloadGroupReference):
overloadGroupNode = try context.entity(with: overloadGroupReference)
overloadGroupSymbol = try XCTUnwrap(overloadGroupNode.semantic as? Symbol)
overloadGroupReferences = try XCTUnwrap(overloadGroupSymbol.overloadsVariants.firstValue)

XCTAssertEqual(overloadGroupReferences.displayIndex, 0)

let unifiedSymbol = try XCTUnwrap(overloadGroupNode.unifiedSymbol)
XCTAssertEqual(unifiedSymbol.uniqueIdentifier, "symbol-1" + SymbolGraph.Symbol.overloadGroupIdentifierSuffix)
}

let overloadedReferences = try ["symbol-1", "symbol-2"]
.map { try XCTUnwrap(context.documentationCache.reference(symbolID: $0)) }

for (index, reference) in overloadedReferences.indexed() {
let overloadedDocumentationNode = try XCTUnwrap(context.documentationCache[reference])
let overloadedSymbol = try XCTUnwrap(overloadedDocumentationNode.semantic as? Symbol)

let overloads = try XCTUnwrap(overloadedSymbol.overloadsVariants.firstValue)

// Make sure that each symbol contains all of its sibling overloads.
XCTAssertEqual(overloads.references.count, overloadedReferences.count - 1)
for (otherIndex, otherReference) in overloadedReferences.indexed() where otherIndex != index {
XCTAssert(overloads.references.contains(otherReference))
}

if overloads.displayIndex == 0 {
// The first declaration in the display list should be the same declaration as
// the overload group page
XCTAssertEqual(overloadedSymbol.declaration.first?.value.declarationFragments, overloadGroupSymbol.declaration.first?.value.declarationFragments)
} else {
// Otherwise, this reference should also be referenced by the overload group
XCTAssert(overloadGroupReferences.references.contains(reference))
}
}
}

// A test helper that creates a symbol with a given identifier and kind.
private func makeSymbol(
name: String = "SymbolName",
Expand Down