Skip to content

Commit 3af1deb

Browse files
authored
[stdlib] Add _forEachField(of:options:body:) function (#32873)
This function walks all the fields of a struct, class, or tuple, and calls `body` with the name, offset, and type of each field. `body` can perform any required work or validation, returning `true` to continue walking fields or `false` to stop immediately.
1 parent 0aabee5 commit 3af1deb

File tree

3 files changed

+741
-21
lines changed

3 files changed

+741
-21
lines changed

stdlib/public/core/ReflectionMirror.swift

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,43 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
99
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
@_silgen_name("swift_isClassType")
14+
internal func _isClassType(_: Any.Type) -> Bool
15+
16+
@_silgen_name("swift_getMetadataKind")
17+
internal func _metadataKind(_: Any.Type) -> UInt
18+
1319
@_silgen_name("swift_reflectionMirror_normalizedType")
1420
internal func _getNormalizedType<T>(_: T, type: Any.Type) -> Any.Type
1521

1622
@_silgen_name("swift_reflectionMirror_count")
1723
internal func _getChildCount<T>(_: T, type: Any.Type) -> Int
1824

25+
@_silgen_name("swift_reflectionMirror_recursiveCount")
26+
internal func _getRecursiveChildCount(_: Any.Type) -> Int
27+
28+
@_silgen_name("swift_reflectionMirror_recursiveChildMetadata")
29+
internal func _getChildMetadata(
30+
_: Any.Type,
31+
index: Int,
32+
outName: UnsafeMutablePointer<UnsafePointer<CChar>?>,
33+
outFreeFunc: UnsafeMutablePointer<NameFreeFunc?>
34+
) -> Any.Type
35+
36+
@_silgen_name("swift_reflectionMirror_recursiveChildOffset")
37+
internal func _getChildOffset(
38+
_: Any.Type,
39+
index: Int
40+
) -> Int
41+
1942
internal typealias NameFreeFunc = @convention(c) (UnsafePointer<CChar>?) -> Void
2043

2144
@_silgen_name("swift_reflectionMirror_subscript")
@@ -168,3 +191,107 @@ extension Mirror {
168191
#endif
169192
}
170193
}
194+
195+
/// Options for calling `_forEachField(of:options:body:)`.
196+
@available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *)
197+
@_spi(Reflection)
198+
public struct _EachFieldOptions: OptionSet {
199+
public var rawValue: UInt32
200+
201+
public init(rawValue: UInt32) {
202+
self.rawValue = rawValue
203+
}
204+
205+
/// Require the top-level type to be a class.
206+
///
207+
/// If this is not set, the top-level type is required to be a struct or
208+
/// tuple.
209+
public static var classType = _EachFieldOptions(rawValue: 1 << 0)
210+
211+
/// Ignore fields that can't be introspected.
212+
///
213+
/// If not set, the presence of things that can't be introspected causes
214+
/// the function to immediately return `false`.
215+
public static var ignoreUnknown = _EachFieldOptions(rawValue: 1 << 1)
216+
}
217+
218+
/// The metadata "kind" for a type.
219+
@available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *)
220+
@_spi(Reflection)
221+
public enum _MetadataKind: UInt {
222+
// With "flags":
223+
// runtimePrivate = 0x100
224+
// nonHeap = 0x200
225+
// nonType = 0x400
226+
227+
case `class` = 0
228+
case `struct` = 0x200 // 0 | nonHeap
229+
case `enum` = 0x201 // 1 | nonHeap
230+
case optional = 0x202 // 2 | nonHeap
231+
case foreignClass = 0x203 // 3 | nonHeap
232+
case opaque = 0x300 // 0 | runtimePrivate | nonHeap
233+
case tuple = 0x301 // 1 | runtimePrivate | nonHeap
234+
case function = 0x302 // 2 | runtimePrivate | nonHeap
235+
case existential = 0x303 // 3 | runtimePrivate | nonHeap
236+
case metatype = 0x304 // 4 | runtimePrivate | nonHeap
237+
case objcClassWrapper = 0x305 // 5 | runtimePrivate | nonHeap
238+
case existentialMetatype = 0x306 // 6 | runtimePrivate | nonHeap
239+
case heapLocalVariable = 0x400 // 0 | nonType
240+
case heapGenericLocalVariable = 0x500 // 0 | nonType | runtimePrivate
241+
case errorObject = 0x501 // 1 | nonType | runtimePrivate
242+
case unknown = 0xffff
243+
244+
init(_ type: Any.Type) {
245+
let v = _metadataKind(type)
246+
if let result = _MetadataKind(rawValue: v) {
247+
self = result
248+
} else {
249+
self = .unknown
250+
}
251+
}
252+
}
253+
254+
/// Calls the given closure on every field of the specified type.
255+
///
256+
/// If `body` returns `false` for any field, no additional fields are visited.
257+
///
258+
/// - Parameters:
259+
/// - type: The type to inspect.
260+
/// - options: Options to use when reflecting over `type`.
261+
/// - body: A closure to call with information about each field in `type`.
262+
/// The parameters to `body` are a pointer to a C string holding the name
263+
/// of the field, the offset of the field in bytes, the type of the field,
264+
/// and the `_MetadataKind` of the field's type.
265+
/// - Returns: `true` if every invocation of `body` returns `true`; otherwise,
266+
/// `false`.
267+
@available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *)
268+
@discardableResult
269+
@_spi(Reflection)
270+
public func _forEachField(
271+
of type: Any.Type,
272+
options: _EachFieldOptions = [],
273+
body: (UnsafePointer<CChar>, Int, Any.Type, _MetadataKind) -> Bool
274+
) -> Bool {
275+
// Require class type iff `.classType` is included as an option
276+
if _isClassType(type) != options.contains(.classType) {
277+
return false
278+
}
279+
280+
let childCount = _getRecursiveChildCount(type)
281+
for i in 0..<childCount {
282+
let offset = _getChildOffset(type, index: i)
283+
284+
var nameC: UnsafePointer<CChar>? = nil
285+
var freeFunc: NameFreeFunc? = nil
286+
let childType = _getChildMetadata(
287+
type, index: i, outName: &nameC, outFreeFunc: &freeFunc)
288+
defer { freeFunc?(nameC) }
289+
let kind = _MetadataKind(childType)
290+
291+
if !body(nameC!, offset, childType, kind) {
292+
return false
293+
}
294+
}
295+
296+
return true
297+
}

0 commit comments

Comments
 (0)