swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.57k stars 10.36k forks source link

[SR-6276] Unable to correctly infer `Value` for new `observe` function on `NSObject` #48826

Open 54d85349-cb6a-41a9-b34d-f1337b898fac opened 7 years ago

54d85349-cb6a-41a9-b34d-f1337b898fac commented 7 years ago
Previous ID SR-6276
Radar None
Original Reporter @everlof
Type Bug
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 0 | |Component/s | Compiler | |Labels | Bug, DiagnosticsQoI, TypeChecker | |Assignee | @xedin | |Priority | Medium | md5: c8f5eae56fa3e05644eb87588f7c5551

Issue Description:

I believe I should be able to have a generic class like below, that can listen to changes for any attribute of `T` as long as `T` is a subclass of `NSObject`

class Row<T> {

    let model: T

    init(model: T) {
        self.model = model
    }

    func bind<Value>(to targetKeyPath: ReferenceWritableKeyPath<T, Value>) {
        if let model = model as? NSObject {
            model.observe(targetKeyPath, options: [.new,.old], changeHandler: { _, _ in
                // TADA
            })
        }
    }

}

But I get this error: Generic parameter 'Value' could not be inferred

I'd use this for example like this:

        class Person: NSObject {
            @objc var age: NSNumber?
        }

        class Book: NSObject {
            @objc var title: String?
        }

        let row1 = Row(model: Person())
        let row2 = Row(model: Book())

        row1.bind(to: \Person.age)
        row2.bind(to: \Book.title)
belkadan commented 7 years ago

Hm. I think the problem is that observe(_:options:changeHandler:) checks to make sure the type of the key path matches up with the type of the receiver, and you've lost that once you cast to NSObject. But the type checker should be able to tell you that instead of complaining about Value.

Workaround, or perhaps a more correct API anyway: declare the generic parameter as T: NSObject and drop the cast, or put the bind method in a constrained extension.

belkadan commented 7 years ago

cc @xedin

54d85349-cb6a-41a9-b34d-f1337b898fac commented 7 years ago

@belkadan Alright, thank you for the info. I didn't want to limit the `T` to `NSObject` since I wanted to observe-feature to be optional, but I guess that's how it has to be done.

belkadan commented 7 years ago

You can still do that with a constrained extension:

extension Row where T: NSObject {
  func bind<Value>(to targetKeyPath: ReferenceWritableKeyPath<T, Value>) { … }
}