-
Notifications
You must be signed in to change notification settings - Fork 94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
can get/set properties via keypath? #107
Comments
What do you mean? Using the |
e.g.: let info = try typeInfo(of: User.self)
let property = try info.property(named: \.userName) |
There's not a way builtin to the library. But you could just get the offset of the KeyPath and find the property that has the same offset. let offset = MemoryLayout<Foo>.offset(of: \.bar)
let property = info.properties.first{ $0.offset == offset } |
OK, I'll try it ASAP, Thanks! |
Hi, I found this issue as I'm trying to get the property name from a keyPath. Using the offset above works for a normal keyPath ( |
This seems to do the trick as a brute force approach, though has to traverse the whole tree: import Runtime
extension KeyPath {
var propertyName: String? {
guard let offset = MemoryLayout<Root>.offset(of: self) else {
return nil
}
guard let info = try? typeInfo(of: Root.self) else {
return nil
}
func getPropertyName(for info: TypeInfo, path: [String]) -> String? {
if let property = info.properties.first(where: { $0.offset == offset }) {
return (path + [property.name]).joined(separator: ".")
} else {
for property in info.properties {
if let info = try? typeInfo(of: property.type),
let propertyName = getPropertyName(for: info, path: path + [property.name]) {
return propertyName
}
}
return nil
}
}
return getPropertyName(for: info, path: [])
}
} |
There really isn't a great way to do this as far as I'm aware. The solution above doesn't work for me for a few cases. The offset when it is in a child object will be relative to it's offset in the parent. This may work better: extension KeyPath {
var propertyName: String? {
guard let offset = MemoryLayout<Root>.offset(of: self) else {
return nil
}
guard let info = try? typeInfo(of: Root.self) else {
return nil
}
func getPropertyName(for info: TypeInfo, baseOffset: Int, path: [String]) -> String? {
for property in info.properties {
// Make sure to check the type as well as the offset. In the case of
// something like \Foo.bar.baz, if baz is the first property of bar, they
// will have the same offset since it will be at the top (offset 0).
if property.offset == offset - baseOffset && property.type == Value.self {
return (path + [property.name]).joined(separator: ".")
}
guard let propertyTypeInfo = try? typeInfo(of: property.type) else { continue }
let trueOffset = baseOffset + property.offset
let byteRange = trueOffset..<(trueOffset + propertyTypeInfo.size)
if byteRange.contains(offset) {
// The property is not this property but is within the byte range used by the value.
// So check its properties for the value at the offset.
return getPropertyName(
for: propertyTypeInfo,
baseOffset: property.offset + baseOffset,
path: path + [property.name]
)
}
}
return nil
}
return getPropertyName(for: info, baseOffset: 0, path: [])
}
} This still won't always work though. If the child object is a class or a computed property it will fail, but if its all structs it seems to work. Example: struct Foo {
let a: Int
let bar: Bar
}
struct Bar {
let b: Int
let baz: Baz
}
struct Baz {
let c: Int
}
let path = \Foo.bar.baz.c
print(path.propertyName) // prints "bar.baz.c" |
Oh that’s much better, many thanks! 🙏 |
@wickwirew it seems not work in a class |
class C {
var x: Int = 0
var y: Int = 0
var z: Int = 0
}
print(MemoryLayout<C>.offset(of: \.x)) // nil
print(MemoryLayout<C>.offset(of: \.y)) // nil
print(MemoryLayout<C>.offset(of: \.z)) // nil |
here is the KeyPaths implementation of Apple/swift: @usableFromInline // Exposed as public API by MemoryLayout<Root>.offset(of:)
internal var _storedInlineOffset: Int? {
return withBuffer {
var buffer = $0
// The identity key path is effectively a stored keypath of type Self
// at offset zero
if buffer.data.isEmpty { return 0 }
var offset = 0
while true {
let (rawComponent, optNextType) = buffer.next()
switch rawComponent.header.kind {
case .struct:
offset += rawComponent._structOrClassOffset
case .class, .computed, .optionalChain, .optionalForce, .optionalWrap, .external:
return .none
}
if optNextType == nil { return .some(offset) }
}
}
}
} |
@alexlee002 yea that is actually the expected behavior due to the way |
How can I do that?
The text was updated successfully, but these errors were encountered: