File: DocumentationDataVariants.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 (188 lines) | stat: -rw-r--r-- 8,624 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
/*
 This source file is part of the Swift.org open source project

 Copyright (c) 2021-2022 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
import SymbolKit

/// A model type that encapsulates variants of documentation node data.
///
/// Use this type to represent a piece of information about a documentation node that can have different values depending on some trait,
/// e.g., the programming language the symbol was defined in.
public struct DocumentationDataVariants<Variant> {
    /// The variant values for this collection of variants.
    private var values: [DocumentationDataVariantsTrait: Variant]
    
    /// The default value of the variant.
    private var defaultVariantValue: Variant?
    
    /// All the variants registered in this variant collection, including any default variant.
    ///
    /// The default variant value, if one exists, is the last element of the returned array.
    public var allValues: [(trait: DocumentationDataVariantsTrait, variant: Variant)] {
        values.map { (trait: $0.key, variant: $0.value) }
            // Append the default variant value if there is one.
            + (defaultVariantValue.map { [(.fallback, $0)] } ?? [])
    }
    
    /// Whether there are any variants for this piece of information about the documentation node
    public var isEmpty: Bool {
        values.isEmpty
    }
    
    /// Creates a variants value.
    ///
    /// - Parameters:
    ///   - values: The variants for a piece of information about a documentation node, grouped by trait, e.g., programming language.
    ///   - defaultVariantValue: The default value for this piece of information about the documentation node, if no variants have been registered.
    public init(values: [DocumentationDataVariantsTrait: Variant] = [:], defaultVariantValue: Variant? = nil) {
        self.values = values
        self.defaultVariantValue = defaultVariantValue
    }
    
    /// Accesses the variant for the given trait.
    public subscript(trait: DocumentationDataVariantsTrait) -> Variant? {
        get { values[trait] ?? defaultVariantValue }
        set {
            if trait == .fallback {
                defaultVariantValue = newValue
            } else {
                values[trait] = newValue
            }
        }
    }
    
    /// Accesses the variant for the given trait,
    /// falling back to the given default variant if the key isn’t found.
    public subscript(trait: DocumentationDataVariantsTrait, default defaultValue: Variant) -> Variant {
        get { values[trait] ?? defaultValue }
        set {
            if trait == .fallback {
                defaultVariantValue = newValue
            } else {
                values[trait] = newValue
            }
        }
    }
    
    /// Whether a variant for the given trait has been registered.
    ///
    /// - Parameter trait: The trait to look up a variant for.
    public func hasVariant(for trait: DocumentationDataVariantsTrait) -> Bool {
        values.keys.contains(trait)
    }
    
    func map<NewVariant>(transform: (Variant) -> NewVariant) -> DocumentationDataVariants<NewVariant> {
        return DocumentationDataVariants<NewVariant>(
            values: Dictionary(
                uniqueKeysWithValues: values.map { (trait, variant) in
                    return (trait, transform(variant))
                }
            ),
            defaultVariantValue: defaultVariantValue.map(transform)
        )
    }
}

extension DocumentationDataVariants {
    /// Convenience initializer to initialize a variants value with a Swift variant only.
    init(swiftVariant: Variant?) {
        if let swiftVariant {
            self.init(values: [.swift: swiftVariant])
        } else {
            self.init()
        }
    }
    
    static var empty: DocumentationDataVariants<Variant> {
        return DocumentationDataVariants<Variant>(values: [:], defaultVariantValue: nil)
    }
    
    /// Convenience API to access the first variant, or the default value if there are no registered variants.
    ///
    /// > Important:
    /// > Do not use this property in new code.
    /// > It exists to transition existing code from only working with Swift symbols to working with multi-language symbols.
    /// > This property should be considered deprecated but isn't formally deprecated to avoid the ~50 warnings that would make it
    /// > harder to spot new warnings. (rdar://86580516)
    var firstValue: Variant? {
        // A Dictionary's order isn't stable across program executions so accessing the `first` value would
        // result in non-deterministic behavior and also flaky tests.
        //
        // Since this convenience accessor exist to transition existing code from only working with Swift symbols,
        // it accesses the Swift value first, if it exist, and otherwise accesses the real non-deterministic first value.
        // This assumes that variant only represents one non-Swift language.
        get { self[.swift] ?? self.values.first?.value }
        set { self[.swift] = newValue }
    }
}

extension DocumentationDataVariants: Equatable where Variant: Equatable {}

/// The trait associated with a variant of some piece of information about a documentation node.
public struct DocumentationDataVariantsTrait: Hashable {
    /// The Swift programming language.
    public static var swift = DocumentationDataVariantsTrait(interfaceLanguage: SourceLanguage.swift.id)
    
    /// The Objective-C programming language.
    public static var objectiveC = DocumentationDataVariantsTrait(interfaceLanguage: SourceLanguage.objectiveC.id)
    
    /// The language in which the documentation node is relevant.
    public var interfaceLanguage: String?
    
    /// A special trait that represents the fallback trait, which internal clients can use to access the default value of a collection of variants.
    static var fallback = DocumentationDataVariantsTrait()
    
    /// Creates a new trait given an interface language.
    ///
    /// - Parameter interfaceLanguage: The language in which a documentation node is relevant.
    public init(interfaceLanguage: String? = nil) {
        self.interfaceLanguage = interfaceLanguage
    }

    /// Creates a new trait given a symbol graph selector.
    ///
    /// - Parameter selector: The symbol graph selector to use when creating the trait.
    public init(for selector: UnifiedSymbolGraph.Selector) {
        self.init(
            interfaceLanguage: SourceLanguage(knownLanguageIdentifier: selector.interfaceLanguage)?.id
                ?? selector.interfaceLanguage
        )
    }
}

extension Set<DocumentationDataVariantsTrait> {
    /// Filters set to a subset of language traits that can coexist together.
    /// 
    /// - Parameter trait: The language variant being processed.
    /// 
    /// Due to the interoperability of Swift and Objective-C, these languages represent separate views into the same APIs rather than being disjoint APIs.
    /// When constructing content, a page wants to display one or the other, not both, while other language variants are distinct and may be used concurrently.
    /// Use `compatible(withTrait:)` to obtain a subset of the current set of variants that can coexist on a page in the context of the input `trait`.
    func traitsCompatible(with trait: DocumentationDataVariantsTrait) -> Set<Element> {
        let compatibleTraits: Set<Element>
        if trait == DocumentationDataVariantsTrait.objectiveC {
            // Objective-C pages should exclude Swift content.
            compatibleTraits = self.filter { $0 != DocumentationDataVariantsTrait.swift }
        } else if trait == DocumentationDataVariantsTrait.swift {
            // Swift pages should exclude Objective-C content.
            compatibleTraits = self.filter { $0 != DocumentationDataVariantsTrait.objectiveC }
        } else {
            // Other pages should allow either Swift (preferred) or Objective-C, but not both.
            if self.contains(DocumentationDataVariantsTrait.swift) {
                // A Swift variant is present, so exclude the Objective-C variant, if present.
                compatibleTraits = self.filter { $0 != DocumentationDataVariantsTrait.objectiveC }
            } else {
                // Full list lacks Swift, so can be used as is.
                compatibleTraits = self
            }
        }
        return compatibleTraits
    }
}