@@ -69,7 +69,7 @@ public struct TypeInfo: Sendable {
69
69
/// - mangled: The mangled name of the type, if available.
70
70
init ( fullyQualifiedName: String , unqualifiedName: String , mangledName: String ? ) {
71
71
self . init (
72
- fullyQualifiedNameComponents: fullyQualifiedName . split ( separator : " . " ) . map ( String . init ) ,
72
+ fullyQualifiedNameComponents: Self . fullyQualifiedNameComponents ( ofTypeWithName : fullyQualifiedName ) ,
73
73
unqualifiedName: unqualifiedName,
74
74
mangledName: mangledName
75
75
)
@@ -95,10 +95,85 @@ public struct TypeInfo: Sendable {
95
95
96
96
// MARK: - Name
97
97
98
+ /// Split a string with a separator while respecting raw identifiers and their
99
+ /// enclosing backtick characters.
100
+ ///
101
+ /// - Parameters:
102
+ /// - string: The string to split.
103
+ /// - separator: The character that separates components of `string`.
104
+ /// - maxSplits: The maximum number of splits to perform on `string`. The
105
+ /// resulting array contains up to `maxSplits + 1` elements.
106
+ ///
107
+ /// - Returns: An array of substrings of `string`.
108
+ ///
109
+ /// Unlike `String.split(separator:maxSplits:omittingEmptySubsequences:)`, this
110
+ /// function does not split the string on separator characters that occur
111
+ /// between pairs of backtick characters. This is useful when splitting strings
112
+ /// containing raw identifiers.
113
+ ///
114
+ /// - Complexity: O(_n_), where _n_ is the length of `string`.
115
+ func rawIdentifierAwareSplit< S> ( _ string: S , separator: Character , maxSplits: Int = . max) -> [ S . SubSequence ] where S: StringProtocol {
116
+ var result = [ S . SubSequence] ( )
117
+
118
+ var inRawIdentifier = false
119
+ var componentStartIndex = string. startIndex
120
+ for i in string. indices {
121
+ let c = string [ i]
122
+ if c == " ` " {
123
+ // We are either entering or exiting a raw identifier. While inside a raw
124
+ // identifier, separator characters are ignored.
125
+ inRawIdentifier. toggle ( )
126
+ } else if c == separator && !inRawIdentifier {
127
+ // Add everything up to this separator as the next component, then start
128
+ // a new component after the separator.
129
+ result. append ( string [ componentStartIndex ..< i] )
130
+ componentStartIndex = string. index ( after: i)
131
+
132
+ if result. count == maxSplits {
133
+ // We don't need to find more separators. We'll add the remainder of the
134
+ // string outside the loop as the last component, then return.
135
+ break
136
+ }
137
+ }
138
+ }
139
+ result. append ( string [ componentStartIndex... ] )
140
+
141
+ return result
142
+ }
143
+
98
144
extension TypeInfo {
99
145
/// An in-memory cache of fully-qualified type name components.
100
146
private static let _fullyQualifiedNameComponentsCache = Locked < [ ObjectIdentifier : [ String ] ] > ( )
101
147
148
+ /// Split the given fully-qualified type name into its components.
149
+ ///
150
+ /// - Parameters:
151
+ /// - fullyQualifiedName: The string to split.
152
+ ///
153
+ /// - Returns: The components of `fullyQualifiedName` as substrings thereof.
154
+ static func fullyQualifiedNameComponents( ofTypeWithName fullyQualifiedName: String ) -> [ String ] {
155
+ var components = rawIdentifierAwareSplit ( fullyQualifiedName, separator: " . " )
156
+
157
+ // If a type is extended in another module and then referenced by name,
158
+ // its name according to the String(reflecting:) API will be prefixed with
159
+ // "(extension in MODULE_NAME):". For our purposes, we never want to
160
+ // preserve that prefix.
161
+ if let firstComponent = components. first, firstComponent. starts ( with: " (extension in " ) ,
162
+ let moduleName = rawIdentifierAwareSplit ( firstComponent, separator: " : " , maxSplits: 1 ) . last {
163
+ // NOTE: even if the module name is a raw identifier, it comprises a
164
+ // single identifier (no splitting required) so we don't need to process
165
+ // it any further.
166
+ components [ 0 ] = moduleName
167
+ }
168
+
169
+ // If a type is private or embedded in a function, its fully qualified
170
+ // name may include "(unknown context at $xxxxxxxx)" as a component. Strip
171
+ // those out as they're uninteresting to us.
172
+ components = components. filter { !$0. starts ( with: " (unknown context at " ) }
173
+
174
+ return components. map ( String . init)
175
+ }
176
+
102
177
/// The complete name of this type, with the names of all referenced types
103
178
/// fully-qualified by their module names when possible.
104
179
///
@@ -121,22 +196,7 @@ extension TypeInfo {
121
196
return cachedResult
122
197
}
123
198
124
- var result = String ( reflecting: type)
125
- . split ( separator: " . " )
126
- . map ( String . init)
127
-
128
- // If a type is extended in another module and then referenced by name,
129
- // its name according to the String(reflecting:) API will be prefixed with
130
- // "(extension in MODULE_NAME):". For our purposes, we never want to
131
- // preserve that prefix.
132
- if let firstComponent = result. first, firstComponent. starts ( with: " (extension in " ) {
133
- result [ 0 ] = String ( firstComponent. split ( separator: " : " , maxSplits: 1 ) . last!)
134
- }
135
-
136
- // If a type is private or embedded in a function, its fully qualified
137
- // name may include "(unknown context at $xxxxxxxx)" as a component. Strip
138
- // those out as they're uninteresting to us.
139
- result = result. filter { !$0. starts ( with: " (unknown context at " ) }
199
+ let result = Self . fullyQualifiedNameComponents ( ofTypeWithName: String ( reflecting: type) )
140
200
141
201
Self . _fullyQualifiedNameComponentsCache. withLock { fullyQualifiedNameComponentsCache in
142
202
fullyQualifiedNameComponentsCache [ ObjectIdentifier ( type) ] = result
0 commit comments