# Enumerating all property key paths in class and superclasses

**URL:** <https://forums.swift.org/t/enumerating-all-property-key-paths-in-class-and-superclasses/81157>\
**Category:** Using Swift\
**Tags:** keypaths, reflection\
**Created:** [July 17, 2025, 9:33am UTC](https://forums.swift.org/t/enumerating-all-property-key-paths-in-class-and-superclasses/81157 "2025-07-17T09:33:10Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![lukeh](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/lukeh/32/31648_2.png) [@lukeh](https://forums.swift.org/u/lukeh)\
**Post date:** [July 17, 2025, 9:33am UTC](https://forums.swift.org/t/enumerating-all-property-key-paths-in-class-and-superclasses/81157/1 "2025-07-17T09:33:10Z")

</div>

I've been using a fork of @philipturner's [swift-reflection-mirror](https://github.com/philipturner/swift-reflection-mirror/blob/a8f66fda2e8713955323483365fe711a56b91974/Sources/ReflectionMirror/ReflectionMirror.swift) in [SwiftOCA](https://github.com/PADL/SwiftOCA) to enumerate all property key paths in a class and its superclasses. Obviously, no expectation this was ever supported or guaranteed to not break.

Note that this code has one crucial difference from the stdlib code on which is is based: `_forEachField()` _does_ allow the `.classType` option (which is fine for me, I don't care about computed properties, and it worked on 6.1 and below).

```swift
  // Require class type iff `.classType` is included as an option
  if _isClassType(type) != options.contains(.classType) {
    return false
  }

```

Anyway, it does appear to break with Swift 6.2, crashing in `swift_getAtPartialKeyPath()`:

```swift
***Program crashed: System trap at 0x0000f0027edcae44***

Platform: arm64 Linux (Ubuntu 24.04.2 LTS)

Thread 0 crashed:

  0 0x0000f0027edcae44 _assertionFailure(_:_:file:line:flags:) + 284 in libswiftCore.so
  1 [ra] 0x0000f0027edcb1c4 _fatalErrorMessage(_:_:file:line:flags:) + 39 in libswiftCore.so
  2 [ra] 0x0000f0027ecdad28 closure #1 in closure #2 in closure #2 in KeyPath._projectReadOnly(from:) + 207 in libswiftCore.so
  3 [ra] [thunk] 0x0000f0027edb4bd4 partial apply for closure #1 in closure #1 in closure #1 in ReferenceWritableKeyPath._projectMutableAddress(from:) + 27 in libswiftCore.so
  4 [ra] [thunk] 0x0000f0027edb8bb8 partial apply for closure #1 in closure #2 in closure #2 in KeyPath._projectReadOnly(from:) + 11 in libswiftCore.so
  5 [ra] 0x0000f0027ecd9694 specialized UnsafeMutableRawBufferPointer.withMemoryRebound<A, B, C>(to:_:) + 95 in libswiftCore.so
  6 [ra] 0x0000f0027ecd8f64 closure #2 in closure #2 in KeyPath._projectReadOnly(from:) + 99 in libswiftCore.so
  7 [ra] 0x0000f0027ee3fc7c KeyPath._projectReadOnly(from:) + 935 in libswiftCore.so
  8 [ra] 0x0000f0027ee40468 swift_getAtPartialKeyPath + 111 in libswiftCore.so
  9 [ra] 0x0000ad74f7ac0f60 OcaRoot.onPropertyEvent@Sendable (event:eventData:) + 575 in SwiftOCAPackageTests.xctest at /home/lukeh/CVSRoot/padl/SwiftOCA/Sources/SwiftOCA/OCC/ControlClasses/Root.swift:240:26
 10 [ra] [system] 0x0000ad74f7ab5ba8 implicit closure #2 in implicit closure #1 in OcaRoot.subscribe() + 99 in SwiftOCAPackageTests.xctest at /home/lukeh/CVSRoot/padl/SwiftOCA/Sources/SwiftOCA/OCC/ControlClasses/Root.swift
 11 [ra] [thunk] 0x0000ad74f7ab5bf4 thunk for @escaping @callee_guaranteed @Sendable (@unowned OcaEvent, @guaranteed Data) -> () + 59 in SwiftOCAPackageTests.xctest at //<compiler-generated>
 12 [ra] 0x0000f0027ead8e70 swift::runJobInEstablishedExecutorContext(swift::Job*) + 411 in libswift_Concurrency.so
 13 [ra] 0x0000f0027ead9b98 swift_job_run + 167 in libswift_Concurrency.so
 14 [ra] 0x0000f0027e7dba7c _dispatch_continuation_pop + 267 in libdispatch.so
 15 [ra] 0x0000f0027e7db8b8 _dispatch_async_redirect_invoke + 179 in libdispatch.so
 16 [ra] 0x0000f0027e7e5118 _dispatch_worker_thread + 431 in libdispatch.so
 17 [ra] 0x0000f0027d21595c start_thread + 891 in libc.so.6
...

```

The code that generates the key paths is below:

```swift
package func _allKeyPaths<T: AnyObject>(value: T) -> [String: PartialKeyPath<T>] {
  var keyPaths = [String: PartialKeyPath<T>]()
  _forEachFieldWithKeyPath(value: value, options: [.classType, .ignoreUnknown]) { field, path in
    let fieldName = String(cString: field)
    keyPaths[fieldName] = path
    return true
  }
  return keyPaths
}

```

I previously used Mirror but it wasn't particularly performant. (Although, caching the result of Mirror might be one option.)

---

<div class="post-metadata">

**Author:** ![lukeh](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/lukeh/32/31648_2.png) [@lukeh](https://forums.swift.org/u/lukeh)\
**Post date:** [July 18, 2025, 7:09am UTC](https://forums.swift.org/t/enumerating-all-property-key-paths-in-class-and-superclasses/81157/2 "2025-07-18T07:09:28Z")

</div>

Looks like there were significant changes to `_projectReadOnly()` between 6.1 and 6.2, which now seem to cause problems when a key path generated by `_forEachFieldWithKeyPath` is passed in. (SE-0369 notes that these key paths are ’incomplete’.)

It's going to take me a moment to go through it but it seems likely the crash point is the `fatalError()` call, when neither `isLast` or `isBreak` are true.

@Alejandro, any thoughts?

---

<div class="post-metadata">

**Author:** ![lukeh](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/lukeh/32/31648_2.png) [@lukeh](https://forums.swift.org/u/lukeh)\
**Post date:** [July 19, 2025, 3:18am UTC](https://forums.swift.org/t/enumerating-all-property-key-paths-in-class-and-superclasses/81157/3 "2025-07-19T03:18:50Z")

</div>

The fix was to use `_createOffsetBasedKeyPath()` (which is exported by stdlib) to resolve the key path, and change my APIs to use `AnyKeyPath` rather than `PartialKeyPath<CommonSuperclass>` (which makes sense, see @Joe_Groff's post on contra variance [here](https://forums.swift.org/t/keypath-value-type-conversion/11812/2)).
