Skip to content

Commit 95a45d0

Browse files
mayaeppsd-ronnqvistQuietMisdreavus
authored
Identify overloaded symbols (#740)
Co-authored-by: David Rönnqvist <[email protected]> Co-authored-by: Vera Mitchell <[email protected]>
1 parent afd0a24 commit 95a45d0

File tree

19 files changed

+3064
-18
lines changed

19 files changed

+3064
-18
lines changed

Sources/SwiftDocC/Infrastructure/DocumentationContext.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,6 +1327,8 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
13271327
}
13281328
emitWarningsForSymbolsMatchedInMultipleDocumentationExtensions(with: symbolsWithMultipleDocumentationExtensionMatches)
13291329
symbolsWithMultipleDocumentationExtensionMatches.removeAll()
1330+
1331+
try groupOverloadedSymbols(with: linkResolver.localResolver)
13301332

13311333
// Create inherited API collections
13321334
try GeneratedDocumentationTopics.createInheritedSymbolsAPICollections(
@@ -2398,6 +2400,37 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
23982400
}
23992401
return automaticallyCuratedSymbols
24002402
}
2403+
2404+
/// Handles overloaded symbols by grouping them together into one page.
2405+
private func groupOverloadedSymbols(with linkResolver: PathHierarchyBasedLinkResolver) throws {
2406+
guard FeatureFlags.current.isExperimentalOverloadedSymbolPresentationEnabled else {
2407+
return
2408+
}
2409+
2410+
try linkResolver.traverseOverloadedSymbols { overloadedSymbolReferences in
2411+
2412+
// Tell each symbol what other symbols overload it.
2413+
for (index, symbolReference) in overloadedSymbolReferences.indexed() {
2414+
let documentationNode = try entity(with: symbolReference)
2415+
2416+
guard let symbol = documentationNode.semantic as? Symbol else {
2417+
preconditionFailure("""
2418+
Only symbols can be overloads. Found non-symbol overload for \(symbolReference.absoluteString.singleQuoted).
2419+
Non-symbols should already have been filtered out in `PathHierarchyBasedLinkResolver.traverseOverloadedSymbols(_:)`.
2420+
""")
2421+
}
2422+
guard symbolReference.sourceLanguage == .swift else {
2423+
assertionFailure("Overload groups is only supported for Swift symbols.")
2424+
continue
2425+
}
2426+
2427+
var otherOverloadedSymbolReferences = overloadedSymbolReferences
2428+
otherOverloadedSymbolReferences.remove(at: index)
2429+
let overloads = Symbol.Overloads(references: otherOverloadedSymbolReferences, displayIndex: index)
2430+
symbol.overloadsVariants = .init(swiftVariant: overloads)
2431+
}
2432+
}
2433+
}
24012434

24022435
/// A closure type getting the information about a reference in a context and returns any possible problems with it.
24032436
public typealias ReferenceCheck = (DocumentationContext, ResolvedTopicReference) -> [Problem]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2023 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import SymbolKit
12+
13+
extension SymbolGraph.Symbol.KindIdentifier {
14+
/// The kinds of symbols whose documentation pages should be grouped as overloads.
15+
static let overloadableKinds: Set<SymbolGraph.Symbol.KindIdentifier> = [
16+
.method,
17+
.typeMethod,
18+
.`func`,
19+
.`init`,
20+
.macro,
21+
.subscript,
22+
.`operator`
23+
]
24+
25+
/// Whether a string representing a symbol kind matches an overloadable symbol kind.
26+
static func isOverloadableKind(_ kind: String) -> Bool {
27+
return overloadableKinds.contains { $0.identifier == kind }
28+
}
29+
}
30+

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,18 @@ extension PathHierarchy {
471471
}
472472
return Array(result) + modules.map { $0.identifier }
473473
}
474+
475+
func traverseOverloadedSymbolGroups(observe: (_ overloadedSymbols: [ResolvedIdentifier]) throws -> Void) rethrows {
476+
for node in lookup.values where node.symbol != nil {
477+
for disambiguation in node.children.values {
478+
for (kind, innerStorage) in disambiguation.storage where innerStorage.count > 1 && SymbolGraph.Symbol.KindIdentifier.isOverloadableKind(kind) {
479+
assert(innerStorage.values.allSatisfy { $0.symbol != nil }, "Only symbols should have symbol kind identifiers (\(kind))")
480+
481+
try observe(innerStorage.values.map(\.identifier))
482+
}
483+
}
484+
}
485+
}
474486
}
475487

476488
// MARK: Removing nodes

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ final class PathHierarchyBasedLinkResolver {
5858
}
5959
}
6060

61+
/// Traverse all symbols of the same kind that have collisions.
62+
func traverseOverloadedSymbols(_ observe: (_ overloadedSymbols: [ResolvedTopicReference]) throws -> Void) rethrows {
63+
try pathHierarchy.traverseOverloadedSymbolGroups() { overloadedSymbols in
64+
try observe(overloadedSymbols.map { resolvedReferenceMap[$0]! })
65+
}
66+
}
67+
6168
/// Returns a list of all the top level symbols.
6269
func topLevelSymbols() -> [ResolvedTopicReference] {
6370
return pathHierarchy.topLevelSymbols().map { resolvedReferenceMap[$0]! }

Sources/SwiftDocC/Model/Rendering/RenderSectionTranslator/DeclarationsSectionTranslator.swift

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,32 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator {
4040
return DeclarationRenderSection.Token(fragment: token, identifier: reference?.absoluteString)
4141
}
4242

43+
func renderOtherDeclarationsTokens(from overloads: Symbol.Overloads) -> DeclarationRenderSection.OtherDeclarations {
44+
var otherDeclarations = [DeclarationRenderSection.OtherDeclarations.Declaration]()
45+
for overloadReference in overloads.references {
46+
guard let overload = try? renderNodeTranslator.context.entity(with: overloadReference).semantic as? Symbol else {
47+
continue
48+
}
49+
50+
let declarationFragments = overload.declarationVariants[trait]?.values.first?.declarationFragments
51+
assert(declarationFragments != nil, "Overloaded symbols must have declaration fragments.")
52+
guard let declarationFragments else { continue }
53+
54+
let declarationTokens = declarationFragments.map(translateFragment)
55+
otherDeclarations.append(
56+
.init(
57+
tokens: declarationTokens,
58+
identifier: overloadReference.absoluteString
59+
)
60+
)
61+
}
62+
return .init(declarations: otherDeclarations, displayIndex: overloads.displayIndex)
63+
}
64+
4365
var declarations = [DeclarationRenderSection]()
4466
for pair in declaration {
4567
let (platforms, declaration) = pair
46-
68+
4769
let renderedTokens = declaration.declarationFragments.map(translateFragment)
4870

4971
let platformNames = platforms.sorted { (lhs, rhs) -> Bool in
@@ -52,12 +74,16 @@ struct DeclarationsSectionTranslator: RenderSectionTranslator {
5274
}
5375
return lhsValue.rawValue < rhsValue.rawValue
5476
}
55-
77+
78+
// If this symbol has overloads, render their declarations as well.
79+
let otherDeclarations = symbol.overloadsVariants[trait].map({ renderOtherDeclarationsTokens(from: $0) })
80+
5681
declarations.append(
5782
DeclarationRenderSection(
5883
languages: [trait.interfaceLanguage ?? renderNodeTranslator.identifier.sourceLanguage.id],
5984
platforms: platformNames,
60-
tokens: renderedTokens
85+
tokens: renderedTokens,
86+
otherDeclarations: otherDeclarations
6187
)
6288
)
6389
}

Sources/SwiftDocC/Model/Rendering/Symbol/DeclarationsRenderSection.swift

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public struct DeclarationRenderSection: Codable, Equatable {
131131
// MARK: - Codable
132132

133133
private enum CodingKeys: CodingKey {
134-
case text, kind, identifier, preciseIdentifier
134+
case text, kind, identifier, preciseIdentifier, otherDeclarations
135135
}
136136

137137
public func encode(to encoder: Encoder) throws {
@@ -157,22 +157,71 @@ public struct DeclarationRenderSection: Codable, Equatable {
157157
}
158158
}
159159

160+
/// Declarations for other symbols that are related to this one, e.g. overloads.
161+
public struct OtherDeclarations: Codable, Equatable {
162+
/// A displayable declaration for a different symbol that is connected to this one.
163+
public struct Declaration: Codable, Equatable {
164+
/// The symbol's declaration tokens.
165+
public let tokens: [Token]
166+
167+
/// The symbol's identifier.
168+
public let identifier: String
169+
170+
/// Creates a new other declaration for a symbol that is connected to this one, e.g. an overload.
171+
public init(tokens: [Token], identifier: String) {
172+
self.tokens = tokens
173+
self.identifier = identifier
174+
}
175+
}
176+
/// The displayable declarations for this symbol's overloads.
177+
let declarations: [Declaration]
178+
/// The index where this symbol's declaration should be displayed (inserted) among the declarations.
179+
let displayIndex: Int
180+
181+
/// Creates a group of declarations for symbols that are connected to this one, e.g. overloads.
182+
public init(declarations: [Declaration], displayIndex: Int) {
183+
self.declarations = declarations
184+
self.displayIndex = displayIndex
185+
}
186+
}
187+
188+
/// The declarations for this symbol's overloads.
189+
public let otherDeclarations: OtherDeclarations?
190+
191+
public enum CodingKeys: CodingKey {
192+
case tokens
193+
case platforms
194+
case languages
195+
case otherDeclarations
196+
}
197+
160198
/// Creates a new declaration section.
161199
/// - Parameters:
162200
/// - languages: The source languages to which this declaration applies.
163201
/// - platforms: The platforms to which this declaration applies.
164202
/// - tokens: The list of declaration tokens.
165-
public init(languages: [String]?, platforms: [PlatformName?], tokens: [Token]) {
203+
/// - otherDeclarations: The declarations for this symbol's overloads.
204+
public init(languages: [String]?, platforms: [PlatformName?], tokens: [Token], otherDeclarations: OtherDeclarations? = nil) {
166205
self.languages = languages
167206
self.platforms = platforms
168207
self.tokens = tokens
208+
self.otherDeclarations = otherDeclarations
169209
}
170210

171211
public init(from decoder: Decoder) throws {
172212
let container = try decoder.container(keyedBy: CodingKeys.self)
173213
tokens = try container.decode([Token].self, forKey: .tokens)
174214
platforms = try container.decode([PlatformName?].self, forKey: .platforms)
175215
languages = try container.decodeIfPresent([String].self, forKey: .languages)
216+
otherDeclarations = try container.decodeIfPresent(OtherDeclarations.self, forKey: .otherDeclarations)
217+
}
218+
219+
public func encode(to encoder: Encoder) throws {
220+
var container = encoder.container(keyedBy: DeclarationRenderSection.CodingKeys.self)
221+
try container.encode(self.tokens, forKey: DeclarationRenderSection.CodingKeys.tokens)
222+
try container.encode(self.platforms, forKey: DeclarationRenderSection.CodingKeys.platforms)
223+
try container.encode(self.languages, forKey: DeclarationRenderSection.CodingKeys.languages)
224+
try container.encodeIfPresent(self.otherDeclarations, forKey: DeclarationRenderSection.CodingKeys.otherDeclarations)
176225
}
177226
}
178227

@@ -195,6 +244,7 @@ extension DeclarationRenderSection: RenderJSONDiffable {
195244
diffBuilder.addDifferences(atKeyPath: \.platforms, forKey: CodingKeys.platforms)
196245
diffBuilder.addDifferences(atKeyPath: \.tokens, forKey: CodingKeys.tokens)
197246
diffBuilder.addDifferences(atKeyPath: \.languages, forKey: CodingKeys.languages)
247+
diffBuilder.addDifferences(atKeyPath: \.otherDeclarations, forKey: CodingKeys.otherDeclarations)
198248

199249
return diffBuilder.differences
200250
}

Sources/SwiftDocC/Semantics/ReferenceResolver.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,8 @@ struct ReferenceResolver: SemanticVisitor {
506506
redirectsVariants: symbol.redirectsVariants,
507507
crossImportOverlayModule: symbol.crossImportOverlayModule,
508508
originVariants: symbol.originVariants,
509-
automaticTaskGroupsVariants: symbol.automaticTaskGroupsVariants
509+
automaticTaskGroupsVariants: symbol.automaticTaskGroupsVariants,
510+
overloadsVariants: symbol.overloadsVariants
510511
)
511512
}
512513

Sources/SwiftDocC/Semantics/Symbol/Symbol.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,16 @@ public final class Symbol: Semantic, Abstracted, Redirected, AutomaticTaskGroups
235235

236236
/// Any automatically created task groups of the symbol, in each language variant the symbol is available in.
237237
var automaticTaskGroupsVariants: DocumentationDataVariants<[AutomaticTaskGroupSection]>
238+
239+
struct Overloads {
240+
/// References to other symbols that overload this one.
241+
let references: [ResolvedTopicReference]
242+
/// The index where this symbol's should be displayed (inserted) among the overloads declarations.
243+
let displayIndex: Int
244+
}
245+
246+
/// References to other symbols that overload this one.
247+
var overloadsVariants: DocumentationDataVariants<Overloads>
238248

239249
/// Creates a new symbol with the given data.
240250
init(
@@ -268,7 +278,8 @@ public final class Symbol: Semantic, Abstracted, Redirected, AutomaticTaskGroups
268278
redirectsVariants: DocumentationDataVariants<[Redirect]>,
269279
crossImportOverlayModule: (declaringModule: String, bystanderModules: [String])? = nil,
270280
originVariants: DocumentationDataVariants<SymbolGraph.Relationship.SourceOrigin> = .init(),
271-
automaticTaskGroupsVariants: DocumentationDataVariants<[AutomaticTaskGroupSection]> = .init(defaultVariantValue: [])
281+
automaticTaskGroupsVariants: DocumentationDataVariants<[AutomaticTaskGroupSection]> = .init(defaultVariantValue: []),
282+
overloadsVariants: DocumentationDataVariants<Overloads> = .init(defaultVariantValue: nil)
272283
) {
273284
self.kindVariants = kindVariants
274285
self.titleVariants = titleVariants
@@ -331,6 +342,7 @@ public final class Symbol: Semantic, Abstracted, Redirected, AutomaticTaskGroups
331342
self.redirectsVariants = redirectsVariants
332343
self.originVariants = originVariants
333344
self.automaticTaskGroupsVariants = automaticTaskGroupsVariants
345+
self.overloadsVariants = overloadsVariants
334346
}
335347

336348
public override func accept<V: SemanticVisitor>(_ visitor: inout V) -> V.Result {

Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderNode.spec.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2517,9 +2517,48 @@
25172517
"items": {
25182518
"$ref": "#/components/schemas/DeclarationToken"
25192519
}
2520+
},
2521+
"otherDeclarations": {
2522+
"$ref": "#/components/schemas/OtherDeclarations"
25202523
}
25212524
}
25222525
},
2526+
"OtherDeclarations": {
2527+
"required": [
2528+
"declarations",
2529+
"displayIndex"
2530+
],
2531+
"type": "object",
2532+
"properties": {
2533+
"declarations": {
2534+
"type": "array",
2535+
"items": {
2536+
"$ref": "#/components/schemas/OtherDeclaration"
2537+
}
2538+
},
2539+
"displayIndex": {
2540+
"type": "integer"
2541+
}
2542+
}
2543+
},
2544+
"OtherDeclaration": {
2545+
"required": [
2546+
"tokens",
2547+
"identifier"
2548+
],
2549+
"type": "object",
2550+
"properties": {
2551+
"tokens": {
2552+
"type": "array",
2553+
"items": {
2554+
"$ref": "#/components/schemas/DeclarationToken"
2555+
}
2556+
},
2557+
"identifier": {
2558+
"type": "string"
2559+
}
2560+
}
2561+
},
25232562
"DeclarationToken": {
25242563
"required": [
25252564
"text",

Sources/SwiftDocC/Utility/FeatureFlags.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ public struct FeatureFlags: Codable {
2424
/// Whether or not experimental support for emitting a serialized version of the local link resolution information is enabled.
2525
public var isExperimentalLinkHierarchySerializationEnabled = false
2626

27+
/// Whether or not experimental support for combining overloaded symbol pages is enabled.
28+
public var isExperimentalOverloadedSymbolPresentationEnabled = false
29+
2730
/// Creates a set of feature flags with the given values.
2831
///
2932
/// - Parameters:

Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ extension ConvertAction {
2222

2323
FeatureFlags.current.isExperimentalDeviceFrameSupportEnabled = convert.enableExperimentalDeviceFrameSupport
2424
FeatureFlags.current.isExperimentalLinkHierarchySerializationEnabled = convert.enableExperimentalLinkHierarchySerialization
25+
FeatureFlags.current.isExperimentalOverloadedSymbolPresentationEnabled = convert.enableExperimentalOverloadedSymbolPresentation
2526

2627
// If the user-provided a URL for an external link resolver, attempt to
2728
// initialize an `OutOfProcessReferenceResolver` with the provided URL.

0 commit comments

Comments
 (0)