Ambiguous type of expression with dynamic member lookup applied to wrapper holding optional type with property wrapped value

Hi. I'm trying to figure out the way to access projected values of custom wrappers holding optional values and face Type of expression is ambiguous without more context errors in Swift 5.2.4 (Xcode 11.5). Here is the code that I have that works fine with non optional types, and solutions that I tried.

I have a custom wrapper TestWrapper (it could be a property wrapper by itself but I can't figure out proper way to implement DynamicProperty so have to use it as it is) used with @ObservedObject. Then the object that I wrap and observe itself contains a property with another property wrapper. Now I used dynamic member lookup on TestWrapper to access property of wrapped value via key path.

@dynamicMemberLookup
public class TestWrapper<T>: ObservableObject {
    @Published var wrappedValue: T
    
    public init(_ wrappedValue: T) {
        self.wrappedValue = wrappedValue
    }
    
    public subscript<U>(dynamicMember keyPath: KeyPath<T, U>) -> U {
        wrappedValue[keyPath: keyPath]
    }
}

@propertyWrapper
struct TestPropertyWrapper {
    var wrappedValue: String
    var projectedValue: Int { wrappedValue.count }
}

struct TestObject {
    @TestPropertyWrapper
    var string: String
}

Then I can use it with no problems like this:

struct RootView: View {
    @ObservedObject var testValue: TestWrapper<TestObject>
        = TestWrapper(TestObject(string: "hello"))
    
    var body: some View {
        let lenght = testValue.$string
        return EmptyView()
    }
}

But as soon as I change from TestObject to TestObject? I got ambiguous type error (also happens on regular key paths, so projected value key path is not the issue):

struct RootView: View {
    @ObservedObject var testValue: TestWrapper<TestObject?>
        = TestWrapper(TestObject(string: "hello"))
    
    var body: some View {
        let lenght = testValue.$string // Type of expression is ambiguous without more context
        return EmptyView()
    }
}

Now what I have already tried and what didn't solve the issue:

  • define a second dynamic member lookup (or change original implementation) subscript on T?, same error
  • define a protocol to constrain TestWrapper.T that Optional would also implement, same error:
protocol TestProtocol {}
extension Optional: TestProtocol where Wrapped: TestProtocol {}
public class TestWrapper<T: TestProtocol>: ObservableObject { ... }
struct TestObject: TestProtocol { ... }
  • remove property wrapper from var string: String and access it like testValue.string, same result (so property wrapper is not to blame)

Any suggestions how this can be solved?

Hi. Try this:

extension TestWrapper where T: OptionalType {
    subscript<U>(dynamicMember keyPath: KeyPath<T.Wrapped, U>) -> U? {
        wrappedValue.map { $0[keyPath: keyPath] }
    }
}

protocol OptionalType {
    associatedtype Wrapped
    func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
}

extension Optional: OptionalType {}
2 Likes

That worked great, thanks!