New ambiguity with KeyPaths in Swift 5

When compiling my project with Swift 5 I found that the compiler now complains about ambiguity where it previously didn't. Is this a bug or intentional? (/cc @jrose)

import Cocoa

class MsPiggy: NSObject {
    let k = Kermit()
    
    func party() {
        // Here I get `Type of expression is ambiguous without more context`
        // but only with the Swift 5 compiler. In Swift 4.2 mode everything is fine.
        k.a(\.color) { v in }
    }
}

class Kermit: NSObject {
    // If I remove the `private(set)` here, even Swift 5 is happily compiling this
    @objc dynamic private(set) var color: NSColor!
}

extension NSObjectProtocol where Self: NSObject {
    func a<Value>(_ keyPath: ReferenceWritableKeyPath<Self, Value>, onChange: @escaping (Value) -> ()) {}
    func a<Value>(_ keyPath: ReferenceWritableKeyPath<Self, Value>, onChange: @escaping (Self, Value) -> ()) {}
}

swift-5.0-DEVELOPMENT-SNAPSHOT-2019-02-17-a-osx, Xcode 10.2 b3

This sounds like a case where we've regressed on deciding whether something is a tuple splat or not. @xedin would know better than me if it's a known issue.

But why does removing private(set) change the behavior?

Ah, with the private(set) your call is invalid, so maybe it's just being diagnosed incorrectly? This might have been a bug in 4.2 that got fixed (that it was allowed originally), but now you've uncovered a separate bug with diagnostics.

Why was it invalid? I was only reading from the keypath, so the setter shouldn't matter right?

Mhm, the objc runtime would be able to write regardless of the private(set) no?
I was using those methods in KVO. (Sorry the code is just a strawman handpuppet example)

Ah, but your function a takes a ReferenceWritableKeyPath, so party() doesn't know that you're only reading from it.

This looks like a bad error message for a real type error. From MsPiggy.party, Kermit.color is only gettable, so \.color results in a read-only KeyPath. You can't pass that statically as a ReferenceWritableKeyPath. If you make Kermit.color fileprivate, that should address the issue. In earlier versions of Swift, we didn't correctly diagnose this so you should get compatibility warnings, but in Swift 5 mode it is strictly enforced as an error.

1 Like

Yeah in my real code those are in viewmodels and model objects so not in the same file or even framework. KVO always worked so far, but I get the argument about the keypath requiring writability.

You shouldn't need a ReferenceWritableKeyPath for KVO, since there are observable properties that are read-only and that only change value because of implementation-side modifications or because of indirect mutations of dependent state.

2 Likes

Thanks, that makes perfect sense of course and now I feel stupid :slight_smile:. It's kinda obvious even.
Should I file a bug about the misleading error message? If I understand correctly there is no ambiguity, but rather both methods definitely don't match because the key-path definitely doesn't fit.

Filed [SR-9958] Incorrect error message for non-matching signatures with KeyPaths · Issue #52362 · apple/swift · GitHub

2 Likes

Yes, this is related to this source compatibility block - https://github.com/apple/swift/blob/master/lib/Sema/CSSimplify.cpp#L4382L4393, it used to form writable keypath up until swift version 5.