ForEachField.swift 3.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. // Adapted from https://github.com/swiftlang/swift/blob/swift-6.2.3-RELEASE/stdlib/public/core/ReflectionMirror.swift
  2. // NB: I have absolutely zero clue why this is importable here. Xcode just shows
  3. // me an empty file when I command-click it. Nevertheless, it works just fine and
  4. // SourceKit seems to have no trouble finding stuff from here, so I'll roll
  5. // with it for now.
  6. private import SwiftShims
  7. @_silgen_name("swift_reflectionMirror_recursiveCount")
  8. private func getRecursiveChildCount(of: Any.Type) -> Int
  9. @_silgen_name("swift_reflectionMirror_recursiveChildOffset")
  10. private func getChildOffset(of: Any.Type, index: Int) -> Int
  11. @_silgen_name("swift_reflectionMirror_subscript")
  12. private func getChild<T>(
  13. of: T,
  14. type: Any.Type,
  15. index: Int,
  16. outName: UnsafeMutablePointer<UnsafePointer<CChar>?>,
  17. outFreeFunc: UnsafeMutablePointer<NameFreeFunc?>
  18. ) -> Any
  19. /// Calls the given closure on every field of the specified type.
  20. ///
  21. /// The standard library exposes a function named `_forEachField(of:options:body:)` (used
  22. /// [by Combine]), which calls directly into the runtime's reflection facilities in order to get
  23. /// the names and types of the provided value's stored properties, bypassing most of the `Mirror`
  24. /// overhead. However, it's annotated with `@_spi(Reflection)`, and most people's toolchain
  25. /// installations don't include the stdlib's SPI interfaces. So we have to reimplement this
  26. /// function ourselves.
  27. ///
  28. /// There are three runtime functions used to implement this:
  29. /// - `swift_reflectionMirror_recursiveCount(_:)`
  30. /// - `swift_reflectionMirror_recursiveChildOffset(_:index:)`
  31. /// - `swift_reflectionMirror_subscript(_:type:index:outName:outFreeFunc:)`
  32. ///
  33. /// All three of these are public runtime API/ABI, as noted by the docs for the
  34. /// [`SWIFT_RUNTIME_STDLIB_API`] C macro that they are annotated with.
  35. ///
  36. /// - SeeAlso: The [original implementation] as of Swift 6.2.3.
  37. ///
  38. /// [by Combine]: https://forums.swift.org/t/how-is-the-published-property-wrapper-implemented/58223/11
  39. /// [original implementation]: https://github.com/swiftlang/swift/blob/swift-6.2.3-RELEASE/stdlib/public/core/ReflectionMirror.swift#L280-L284
  40. /// [`SWIFT_RUNTIME_STDLIB_API`]: https://github.com/swiftlang/swift/blob/swift-6.2.3-RELEASE/stdlib/public/SwiftShims/swift/shims/Visibility.h#L265-L267
  41. ///
  42. /// - Parameters:
  43. /// - type: The type to inspect.
  44. /// - body: A closure to call with information about each field in `type`.
  45. /// The parameters to `body` are the name of the field, the offset of the
  46. /// field, and the field's value.
  47. func forEachField<Value>(
  48. of value: Value,
  49. body: (_ name: String?, _ offset: Int, _ fieldValue: Any) -> Void
  50. ) {
  51. let childCount = getRecursiveChildCount(of: Value.self)
  52. for index in 0..<childCount {
  53. let offset = getChildOffset(of: Value.self, index: index)
  54. var name: UnsafePointer<CChar>? = nil
  55. var freeFunc: NameFreeFunc? = nil
  56. defer { freeFunc?(name) }
  57. let childValue = getChild(
  58. of: value,
  59. type: Value.self,
  60. index: index,
  61. outName: &name,
  62. outFreeFunc: &freeFunc
  63. )
  64. body(name.flatMap(String.init(validatingCString:)), offset, childValue)
  65. }
  66. }
  67. /// > Safety: You must ensure that the `Base` type has a stored property at `offset` with a type of
  68. /// > `Property` (or another type that can be safely bit-casted to `Property` for all possible
  69. /// > values); breaking these invariants will cause undefined behavior.
  70. func getProperty<Base, Property>(_: Property.Type, of base: Base, at offset: Int) -> Property {
  71. assert(offset + MemoryLayout<Property>.size <= MemoryLayout<Base>.size)
  72. return withUnsafeBytes(of: base) { buffer in
  73. buffer.baseAddress!.advanced(by: offset)
  74. .assumingMemoryBound(to: Property.self)
  75. .pointee
  76. }
  77. }