@@ -95,6 +95,52 @@ public struct TypeInfo: Sendable {
9595
9696// MARK: - Name
9797
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+
98144extension TypeInfo {
99145 /// An in-memory cache of fully-qualified type name components.
100146 private static let _fullyQualifiedNameComponentsCache = Locked < [ ObjectIdentifier : [ String ] ] > ( )
@@ -106,27 +152,14 @@ extension TypeInfo {
106152 ///
107153 /// - Returns: The components of `fullyQualifiedName` as substrings thereof.
108154 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: " . " )
123156
124157 // If a type is extended in another module and then referenced by name,
125158 // its name according to the String(reflecting:) API will be prefixed with
126159 // "(extension in MODULE_NAME):". For our purposes, we never want to
127160 // preserve that prefix.
128161 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 {
130163 // NOTE: even if the module name is a raw identifier, it comprises a
131164 // single identifier (no splitting required) so we don't need to process
132165 // it any further.
0 commit comments