@@ -69,7 +69,7 @@ public struct TypeInfo: Sendable {
6969 /// - mangled: The mangled name of the type, if available.
7070 init ( fullyQualifiedName: String , unqualifiedName: String , mangledName: String ? ) {
7171 self . init (
72- fullyQualifiedNameComponents: fullyQualifiedName . split ( separator : " . " ) . map ( String . init ) ,
72+ fullyQualifiedNameComponents: Self . fullyQualifiedNameComponents ( ofTypeWithName : fullyQualifiedName ) ,
7373 unqualifiedName: unqualifiedName,
7474 mangledName: mangledName
7575 )
@@ -95,10 +95,85 @@ 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 ] ] > ( )
101147
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+
102177 /// The complete name of this type, with the names of all referenced types
103178 /// fully-qualified by their module names when possible.
104179 ///
@@ -121,22 +196,7 @@ extension TypeInfo {
121196 return cachedResult
122197 }
123198
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) )
140200
141201 Self . _fullyQualifiedNameComponentsCache. withLock { fullyQualifiedNameComponentsCache in
142202 fullyQualifiedNameComponentsCache [ ObjectIdentifier ( type) ] = result
0 commit comments