@@ -95,6 +95,52 @@ 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 ] ] > ( )
@@ -106,27 +152,14 @@ extension TypeInfo {
106
152
///
107
153
/// - Returns: The components of `fullyQualifiedName` as substrings thereof.
108
154
static func fullyQualifiedNameComponents( ofTypeWithName fullyQualifiedName: String ) -> [ String ] {
109
- var components = [ Substring] ( )
110
-
111
- var inRawIdentifier = false
112
- var componentStartIndex = fullyQualifiedName. startIndex
113
- for i in fullyQualifiedName. indices {
114
- let c = fullyQualifiedName [ i]
115
- if c == " ` " {
116
- inRawIdentifier. toggle ( )
117
- } else if c == " . " && !inRawIdentifier {
118
- components. append ( fullyQualifiedName [ componentStartIndex ..< i] )
119
- componentStartIndex = fullyQualifiedName. index ( after: i)
120
- }
121
- }
122
- components. append ( fullyQualifiedName [ componentStartIndex... ] )
155
+ var components = rawIdentifierAwareSplit ( fullyQualifiedName, separator: " . " )
123
156
124
157
// If a type is extended in another module and then referenced by name,
125
158
// its name according to the String(reflecting:) API will be prefixed with
126
159
// "(extension in MODULE_NAME):". For our purposes, we never want to
127
160
// preserve that prefix.
128
161
if let firstComponent = components. first, firstComponent. starts ( with: " (extension in " ) ,
129
- let moduleName = firstComponent . split ( separator: " : " , maxSplits: 1 ) . last {
162
+ let moduleName = rawIdentifierAwareSplit ( firstComponent , separator: " : " , maxSplits: 1 ) . last {
130
163
// NOTE: even if the module name is a raw identifier, it comprises a
131
164
// single identifier (no splitting required) so we don't need to process
132
165
// it any further.
0 commit comments