I've been using a fork of @philipturner's swift-reflection-mirror in 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).
// 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():
*** 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:
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.)