1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
|
/*
This source file is part of the Swift.org open source project
Copyright (c) 2024 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception
See https://swift.org/LICENSE.txt for license information
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/
import Foundation
extension SymbolGraph {
/// Create "overload group" symbols based on name and kind collisions.
///
/// For this method, an "overload" is a symbol whose ``Symbol/pathComponents`` and
/// ``Symbol/kind`` are the same as another symbol in the same symbol graph. Such symbols are
/// usually found in languages that allow for function overloading based on parameter or return
/// types.
///
/// When this method is called, it first looks for any symbols with an overloadable symbol kind
/// (see ``Symbol/KindIdentifier/isOverloadableKind``) which collide on both kind and path. It
/// then sorts these colliding symbols in one of two ways:
///
/// 1. If all the colliding symbols have ``Symbol/DeclarationFragments``, these declarations are
/// condensed into strings by their ``Symbol/DeclarationFragments/Fragment/spelling``, which
/// are then sorted.
/// 2. Otherwise, the symbols are sorted by their unique identifier.
///
/// In all cases, if a symbol has deprecation information (whether a deprecation version or an
/// "unconditional deprecation"), it will be sorted after a symbol that is not deprecated.
///
/// The symbol that appears first in this sorting is then cloned to create an "overload group".
/// This symbol will have a unique identifier based on the original symbol, but suffixed with
/// ``Symbol/overloadGroupIdentifierSuffix``. New ``Relationship/Kind/overloadOf``
/// relationships are created between the colliding symbols and the new overload group symbol.
/// In addition, any existing relationships the original symbol had are also cloned for the
/// overload group.
///
/// > Warning: If you are processing this symbol graph into a ``GraphCollector``, using this
/// > method can lead to incorrectly grouped overloads. Use the `createOverloadGroups`
/// > parameter of ``GraphCollector/finishLoading(createOverloadGroups:)`` instead, which will
/// > perform overload grouping over all the collected graphs.
public mutating func createOverloadGroupSymbols() {
struct OverloadKey: Hashable {
let path: [String]
let kind: SymbolGraph.Symbol.KindIdentifier
}
let defaultImplementationSymbols = relationships.filter({ $0.kind == .defaultImplementationOf }).map(\.source)
let symbolsByPath = [OverloadKey: [SymbolGraph.Symbol]](
grouping: symbols.values
.filter({ !defaultImplementationSymbols.contains($0.identifier.precise) })
.filter(\.kind.identifier.isOverloadableKind),
by: { .init(path: $0.pathComponents, kind: $0.kind.identifier) }
)
var newRelationships = [Relationship]()
for overloadSymbols in symbolsByPath.values where overloadSymbols.count > 1 {
let sortedOverloads: [Symbol] = overloadSymbols.sorted(
by: Symbol.sortForOverloads(
orderByDeclaration: overloadSymbols.allSatisfy({ $0.declarationFragments != nil })))
let firstOverload = sortedOverloads.first!
let overloadGroupIdentifier = firstOverload.identifier.precise + Symbol.overloadGroupIdentifierSuffix
var overloadGroupSymbol = firstOverload
overloadGroupSymbol.identifier.precise = overloadGroupIdentifier
overloadGroupSymbol.isVirtual = true
if let simplifiedDeclaration = overloadGroupSymbol.overloadSubheadingFragments() {
overloadGroupSymbol.names.navigator = simplifiedDeclaration
overloadGroupSymbol.names.subHeading = simplifiedDeclaration
}
self.symbols[overloadGroupSymbol.identifier.precise] = overloadGroupSymbol
// Clone the relationships from the first overload and add them to the overload group
for relationship in self.relationships {
if relationship.source == firstOverload.identifier.precise {
var newRelationship = relationship
newRelationship.source = overloadGroupIdentifier
newRelationships.append(newRelationship)
} else if relationship.target == firstOverload.identifier.precise {
var newRelationship = relationship
newRelationship.target = overloadGroupIdentifier
newRelationships.append(newRelationship)
}
}
for overloadIndex in sortedOverloads.indices {
let overloadSymbol = sortedOverloads[overloadIndex]
// Make new 'overloadOf' relationships between the overloaded symbols and the new overload group
newRelationships.append(.init(
source: overloadSymbol.identifier.precise,
target: overloadGroupIdentifier,
kind: .overloadOf,
targetFallback: nil))
// Add overload data to each symbol
let overloadData = Symbol.OverloadData(
overloadGroupIdentifier: overloadGroupIdentifier,
overloadGroupIndex: overloadIndex)
self.symbols[overloadSymbol.identifier.precise]?.mixins[Symbol.OverloadData.mixinKey] = overloadData
}
}
if !newRelationships.isEmpty {
self.relationships.append(contentsOf: newRelationships)
}
}
}
protocol OverloadsSortable {
var declaration: String? { get }
var identifierKey: String { get }
var isDeprecated: Bool { get }
}
extension OverloadsSortable {
static func sortOverloadsByDeclaration(_ lhs: Self, _ rhs: Self) -> Bool {
guard let lhsDeclaration = lhs.declaration, let rhsDeclaration = rhs.declaration else {
preconditionFailure("Attempting to sort overloads by declaration, but one of the overloads did not have a declaration. lhs: '\(lhs.identifierKey)' ('\(lhs.declaration ?? "")') rhs: '\(rhs.identifierKey)' ('\(rhs.declaration ?? "")')")
}
if lhs.isDeprecated == rhs.isDeprecated {
if lhsDeclaration == rhsDeclaration {
return lhs.identifierKey < rhs.identifierKey
} else {
return lhsDeclaration < rhsDeclaration
}
} else {
return !lhs.isDeprecated
}
}
static func sortOverloadsByIdentifier(_ lhs: Self, _ rhs: Self) -> Bool {
if lhs.isDeprecated == rhs.isDeprecated {
return lhs.identifierKey < rhs.identifierKey
} else {
return !lhs.isDeprecated
}
}
static func sortForOverloads(orderByDeclaration: Bool) -> ((Self, Self) -> Bool) {
if orderByDeclaration {
return sortOverloadsByDeclaration
} else {
return sortOverloadsByIdentifier
}
}
}
extension SymbolGraph.Symbol: OverloadsSortable {
var declaration: String? {
self.declarationFragments?.plainText
}
var identifierKey: String {
self.identifier.precise
}
var isDeprecated: Bool {
self.availability?.contains(where: { $0.deprecatedVersion != nil || $0.isUnconditionallyDeprecated }) ?? false
}
}
extension UnifiedSymbolGraph.Symbol: OverloadsSortable {
var declaration: String? {
let uniqueDeclarations = Set(declarationFragments.values.map(\.plainText))
return uniqueDeclarations.sorted().first
}
var identifierKey: String {
self.uniqueIdentifier
}
var isDeprecated: Bool {
self.availability.values.contains(where: { availabilityItems in
availabilityItems.contains(where: { $0.deprecatedVersion != nil || $0.isUnconditionallyDeprecated })
})
}
}
extension SymbolGraph.Symbol {
/// A suffix added to the precise identifier string for overload group symbols created by
/// ``SymbolGraph/createOverloadGroupSymbols()``.
public static let overloadGroupIdentifierSuffix = "::OverloadGroup"
/// Whether the precise identifier string for this symbol contains the suffix added by
/// ``SymbolGraph/createOverloadGroupSymbols()``.
public var isOverloadGroup: Bool {
self.identifier.precise.hasSuffix(Self.overloadGroupIdentifierSuffix)
}
}
extension [SymbolGraph.Symbol.DeclarationFragments.Fragment] {
var plainText: String {
map(\.spelling).joined()
}
}
|