KeyPath composition and optionals

Hi!

I am working on simple @dynamicMemberLookup class that tracks all mutation to a given object.
General idea is as simple as this:

@dynamicMemberLookup
class Writer<Value> {
    internal init(value: Value, didWrite: @escaping (PartialKeyPath<Value>, Value) -> ()) {
        self.value = value
        self.didWrite = didWrite
    }
    
    private var value: Value
    private let didWrite: (PartialKeyPath<Value>, Value) -> ()
    
    subscript<T>(dynamicMember keyPath: WritableKeyPath<Value, T>) -> T {
        get { value[keyPath: keyPath] }
        set {
            value[keyPath: keyPath] = newValue
            didWrite(keyPath, value)
        }
    }
}

Then I move to the nested subscripts: It is cases where I need to compose multiple levels of tracking:

    subscript<T>(nested keyPath: WritableKeyPath<Value, T>) -> Writer<T> {
        Writer<T>(value: value[keyPath: keyPath]) { nestedKeyPath, new in
            self.value[keyPath: keyPath] = new
            let initialKeyPath = keyPath as PartialKeyPath<Value>
            let fullKeyPath = initialKeyPath.appending(path: nestedKeyPath)!
            self.didWrite(fullKeyPath, self.value)
        }
    }

All is good so far. I need to downcast WritableKeyPath to PartialKeyPath for some reason, but it is ok.

Problem is optional chaining.

Imagine structure that I need to keep track:

struct AllCounters {
  var byId[Int: Counter]
}

struct Counter {
  var value: Int
}

So I need to produce Writer with optional nested type:

    subscript<T>(maybe keyPath: WritableKeyPath<Value, T?>) -> Writer<T>? {
        guard let child = value[keyPath: keyPath] else {
            return nil
        }
        
        return Writer<T>(value: child) { nestedKeyPath, new in
            self.value[keyPath: keyPath] = new
            let unwrappedKeyPath = keyPath.appending(path: \.self!)
            let initialKeyPath = unwrappedKeyPath as PartialKeyPath<Value>
            let fullKeyPath = initialKeyPath.appending(path: nestedKeyPath)!
            self.didWrite(fullKeyPath, self.value)
        }
    }

Initiall key path is \AllCounters.byId[0] and it is resolving into Optional<Counter>.
My nestedKeyPath is \Counter.value, which is logical.

When I am trying to append nested one to initial without let unwrappedKeyPath = keyPath.appending(path: \.self!) this force unwrapping I am receiving nil.

Looks like KeyPath<T, U?> and KeyPath<U, V> should be appended and resolved to KeyPath<T, V?> without any problems.

How can I achieve this composition?

Terms of Service

Privacy Policy

Cookie Policy