File: SymbolGraph%2BOverloads.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (202 lines) | stat: -rw-r--r-- 8,801 bytes parent folder | download
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()
    }
}