-
Notifications
You must be signed in to change notification settings - Fork 102
/
Copy pathTypeInfo.swift
353 lines (313 loc) · 11.8 KB
/
TypeInfo.swift
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 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
//
/// A description of the type of a value encountered during testing or a
/// parameter of a test function.
@_spi(ForToolsIntegrationOnly)
public struct TypeInfo: Sendable {
/// An enumeration defining backing storage for an instance of ``TypeInfo``.
private enum _Kind: Sendable {
/// The type info represents a concrete metatype.
///
/// - Parameters:
/// - type: The concrete metatype.
case type(_ type: Any.Type)
/// The type info represents a metatype, but a reference to that metatype is
/// not available at runtime.
///
/// - Parameters:
/// - fullyQualifiedComponents: The fully-qualified name components of the
/// type.
/// - unqualified: The unqualified name of the type.
/// - mangled: The mangled name of the type, if available.
case nameOnly(fullyQualifiedComponents: [String], unqualified: String, mangled: String?)
}
/// The kind of type info.
private var _kind: _Kind
/// The described type, if available.
///
/// If this instance was created from a type name, or if it was previously
/// encoded and decoded, the value of this property is `nil`.
public var type: Any.Type? {
if case let .type(type) = _kind {
return type
}
return nil
}
init(fullyQualifiedName: String, unqualifiedName: String, mangledName: String?) {
_kind = .nameOnly(
fullyQualifiedComponents: fullyQualifiedName.split(separator: ".").map(String.init),
unqualified: unqualifiedName,
mangled: mangledName
)
}
/// Initialize an instance of this type describing the specified type.
///
/// - Parameters:
/// - type: The type which this instance should describe.
init(describing type: Any.Type) {
_kind = .type(type)
}
/// Initialize an instance of this type describing the type of the specified
/// value.
///
/// - Parameters:
/// - value: The value whose type this instance should describe.
init(describingTypeOf value: Any) {
self.init(describing: Swift.type(of: value))
}
}
// MARK: - Name
extension TypeInfo {
/// An in-memory cache of fully-qualified type name components.
private static let _fullyQualifiedNameComponentsCache = Locked<[ObjectIdentifier: [String]]>()
/// The complete name of this type, with the names of all referenced types
/// fully-qualified by their module names when possible.
///
/// The value of this property is equal to ``fullyQualifiedName``, but is
/// split into components. For instance, given the following declaration in
/// the `Example` module:
///
/// ```swift
/// struct A {
/// struct B {}
/// }
/// ```
///
/// The value of this property for the type `A.B` would be
/// `["Example", "A", "B"]`.
public var fullyQualifiedNameComponents: [String] {
switch _kind {
case let .type(type):
if let cachedResult = Self._fullyQualifiedNameComponentsCache.rawValue[ObjectIdentifier(type)] {
return cachedResult
}
var result = String(reflecting: type)
.split(separator: ".")
.map(String.init)
// If a type is extended in another module and then referenced by name,
// its name according to the String(reflecting:) API will be prefixed with
// "(extension in MODULE_NAME):". For our purposes, we never want to
// preserve that prefix.
if let firstComponent = result.first, firstComponent.starts(with: "(extension in ") {
result[0] = String(firstComponent.split(separator: ":", maxSplits: 1).last!)
}
// If a type is private or embedded in a function, its fully qualified
// name may include "(unknown context at $xxxxxxxx)" as a component. Strip
// those out as they're uninteresting to us.
result = result.filter { !$0.starts(with: "(unknown context at") }
Self._fullyQualifiedNameComponentsCache.withLock { fullyQualifiedNameComponentsCache in
fullyQualifiedNameComponentsCache[ObjectIdentifier(type)] = result
}
return result
case let .nameOnly(fullyQualifiedNameComponents, _, _):
return fullyQualifiedNameComponents
}
}
/// The complete name of this type, with the names of all referenced types
/// fully-qualified by their module names when possible.
///
/// The value of this property is equal to ``fullyQualifiedNameComponents``,
/// but is represented as a single string. For instance, given the following
/// declaration in the `Example` module:
///
/// ```swift
/// struct A {
/// struct B {}
/// }
/// ```
///
/// The value of this property for the type `A.B` would be `"Example.A.B"`.
public var fullyQualifiedName: String {
fullyQualifiedNameComponents.joined(separator: ".")
}
/// A simplified name of this type, by leaving the names of all referenced
/// types unqualified, i.e. without module name prefixes.
///
/// The value of this property is equal to the name of the type in isolation.
/// For instance, given the following declaration in the `Example` module:
///
/// ```swift
/// struct A {
/// struct B {}
/// }
/// ```
///
/// The value of this property for the type `A.B` would simply be `"B"`.
public var unqualifiedName: String {
switch _kind {
case let .type(type):
String(describing: type)
case let .nameOnly(_, unqualifiedName, _):
unqualifiedName
}
}
/// The mangled name of this type as determined by the Swift compiler, if
/// available.
///
/// This property is used by other members of ``TypeInfo``. It should not be
/// exposed as API or SPI because the mangled name of a type may include
/// components derived at runtime that vary between processes. A type's
/// mangled name should not be used if its unmangled name is sufficient.
///
/// If the underlying Swift interface is unavailable or if the Swift runtime
/// could not determine the mangled name of the represented type, the value of
/// this property is `nil`.
var mangledName: String? {
guard #available(_mangledTypeNameAPI, *) else {
return nil
}
switch _kind {
case let .type(type):
return _mangledTypeName(type)
case let .nameOnly(_, _, mangledName):
return mangledName
}
}
}
// MARK: - Properties
extension TypeInfo {
/// Whether or not the described type is a Swift `enum` type.
///
/// Per the [Swift mangling ABI](https://github.com/swiftlang/swift/blob/main/docs/ABI/Mangling.rst),
/// enumeration types are mangled as `"O"`.
///
/// - Bug: We use the internal Swift standard library function
/// `_mangledTypeName()` to derive this information. We should use supported
/// API instead. ([swift-#69147](https://github.com/swiftlang/swift/issues/69147))
var isSwiftEnumeration: Bool {
mangledName?.last == "O"
}
/// Whether or not the described type is imported from C, C++, or Objective-C.
///
/// Per the [Swift mangling ABI](https://github.com/swiftlang/swift/blob/main/docs/ABI/Mangling.rst),
/// types imported from C-family languages are placed in a single flat `__C`
/// module. That module has a standardized mangling of `"So"`. The presence of
/// those characters at the start of a type's mangled name indicates that it
/// is an imported type.
///
/// - Bug: We use the internal Swift standard library function
/// `_mangledTypeName()` to derive this information. We should use supported
/// API instead. ([swift-#69146](https://github.com/swiftlang/swift/issues/69146))
var isImportedFromC: Bool {
guard let mangledName, mangledName.count > 2 else {
return false
}
let prefixEndIndex = mangledName.index(mangledName.startIndex, offsetBy: 2)
return mangledName[..<prefixEndIndex] == "So"
}
}
/// Check if a class is a subclass (or equal to) another class.
///
/// - Parameters:
/// - subclass: The (possible) subclass to check.
/// - superclass The (possible) superclass to check.
///
/// - Returns: Whether `subclass` is a subclass of, or is equal to,
/// `superclass`.
func isClass(_ subclass: AnyClass, subclassOf superclass: AnyClass) -> Bool {
if subclass == superclass {
true
} else if let subclassImmediateSuperclass = _getSuperclass(subclass) {
isClass(subclassImmediateSuperclass, subclassOf: superclass)
} else {
false
}
}
// MARK: - Containing types
extension TypeInfo {
/// An instance of this type representing the type immediately containing the
/// described type.
///
/// For instance, given the following declaration in the `Example` module:
///
/// ```swift
/// struct A {
/// struct B {}
/// }
/// ```
///
/// The value of this property for the type `A.B` would describe `A`, while
/// the value for `A` would be `nil` because it has no enclosing type.
var containingTypeInfo: Self? {
let fqnComponents = fullyQualifiedNameComponents
if fqnComponents.count > 2 { // the module is not a type
let fqn = fqnComponents.dropLast().joined(separator: ".")
#if false // currently non-functional
if let type = _typeByName(fqn) {
return Self(describing: type)
}
#endif
let name = fqnComponents[fqnComponents.count - 2]
return Self(fullyQualifiedName: fqn, unqualifiedName: name, mangledName: nil)
}
return nil
}
/// A sequence of instances of this type representing the types that
/// recursively contain it, starting with the immediate parent (if any.)
var allContainingTypeInfo: some Sequence<Self> {
sequence(first: self, next: \.containingTypeInfo).dropFirst()
}
}
// MARK: - CustomStringConvertible, CustomDebugStringConvertible, CustomTestStringConvertible
extension TypeInfo: CustomStringConvertible, CustomDebugStringConvertible {
public var description: String {
unqualifiedName
}
public var debugDescription: String {
fullyQualifiedName
}
}
// MARK: - Equatable, Hashable
extension TypeInfo: Hashable {
/// Check if this instance describes a given type.
///
/// - Parameters:
/// - type: The type to compare against.
///
/// - Returns: Whether or not this instance represents `type`.
public func describes(_ type: Any.Type) -> Bool {
self == TypeInfo(describing: type)
}
public static func ==(lhs: Self, rhs: Self) -> Bool {
switch (lhs._kind, rhs._kind) {
case let (.type(lhs), .type(rhs)):
return lhs == rhs
default:
return lhs.fullyQualifiedNameComponents == rhs.fullyQualifiedNameComponents
}
}
public func hash(into hasher: inout Hasher) {
hasher.combine(fullyQualifiedName)
}
}
// MARK: - Codable
extension TypeInfo: Codable {
/// A simplified version of ``TypeInfo`` suitable for encoding and decoding.
fileprivate struct EncodedForm {
/// The complete name of this type, with the names of all referenced types
/// fully-qualified by their module names when possible.
public var fullyQualifiedName: String
/// A simplified name of this type, by leaving the names of all referenced
/// types unqualified, i.e. without module name prefixes.
public var unqualifiedName: String
/// The mangled name of this type as determined by the Swift compiler, if
/// available.
public var mangledName: String?
}
public func encode(to encoder: any Encoder) throws {
let encodedForm = EncodedForm(fullyQualifiedName: fullyQualifiedName, unqualifiedName: unqualifiedName, mangledName: mangledName)
try encodedForm.encode(to: encoder)
}
public init(from decoder: any Decoder) throws {
let encodedForm = try EncodedForm(from: decoder)
self.init(fullyQualifiedName: encodedForm.fullyQualifiedName, unqualifiedName: encodedForm.unqualifiedName, mangledName: encodedForm.mangledName)
}
}
extension TypeInfo.EncodedForm: Codable {}