KeyPath: Type of expression is ambiguous without more context


(Adrian Zubarev) #1

Is this a bug? I'm not sure why the compiler is unable to infer the key path type or is it unable to pick one specific key path type from the type hierarchy?

struct S {
  var a = 42
  var b = "swift"
}

func foo<T>(_ path: KeyPath<S, T>) {
  switch path {
  case \.a: // Type of expression is ambiguous without more context
    print("a")
  case \.b: // Type of expression is ambiguous without more context
    print("b")
  default:
    break
  }
}

I can workaround this in two ways, either by providing the root type explicitly or by adding a special infix operation.

prefix operator ^

prefix func ^ <Root, Value>(
  keyPath: KeyPath<Root, Value>
) -> KeyPath<Root, Value> {
  return keyPath
}

struct S {
  var a = 42
  var b = "swift"
}

func foo<T>(_ path: KeyPath<S, T>) {
  switch path {
  case \S.a:
    print("a")
  case ^\.b:
    print("b")
  default:
    break
  }
}

(Garth Snyder) #2

How interesting! "Bug" is probably a bit too strong, but the behavior of that no-op ^ operator certainly makes it look like something odd is going on.

Here's a simplified version that avoids the \ notation at the point of ambiguity and uses == instead of ~=:

struct S {
    var a = 42
    var b = "swift"
}

func path() -> KeyPath<S, Int> {
    return \.a
}

func path() -> KeyPath<S, String> {
    return \.b
}

\S.a == path()  // Error: ambiguous use of 'path()'

My guess is that since == and similar functions are perfectly happy to compare any pair of key paths, regardless of type, there is in fact an ambiguity here. I'm not sure how to look up the signatures for operator functions in Xcode, but my guess would be that adding an overload for == of the form

func ==<S, T, U>(_ a: KeyPath<S, T>, _ b: KeyPath<S, U>) -> Bool

would disambiguate because the signature is more specific than the current one, which is probably more like

func ==<S, T, U, V>(_ a: KeyPath<S, T>, _ b: KeyPath<U, V>) -> Bool

Likewise, an overload such as

func ==<S, T>(_ a: KeyPath<S, T>, _ b: KeyPath<S, T>) -> Bool

might remove even more ambiguity.

I have no idea why the ^ operator works here.


(Adrian Zubarev) #3

My only guess is that ^ anchors the type to a KeyPath here and resolves the ambiguity, but it's only a guess.


(Joe Groff) #4

What compiler version of this? @xedin greatly improved key path type checking in Swift 5. This is a bug regardless.


(Adrian Zubarev) #5

I tested in Swift 4.2, I'll check it later with Xcode 10.2 beta 4 and file a bug report.

Apple Swift version 4.2.1 (swiftlang-1000.11.42 clang-1000.11.45.1)
Target: x86_64-apple-darwin18.2.0


(Suyash Srijan) #6

I get the same error on 5.1.


(Suyash Srijan) #7

This is what the expr ends up being:

(keypath_expr type='<null>'
  (component=unresolved_property a type=<null>)
  <<null>>
  (unresolved_dot_expr type='<null>' field 'a' function_ref=unapplied
    (key_path_dot_expr implicit type='<null>')))

Let me see if I can figure out why this happens.


(Adrian Zubarev) #8

Just a small update, I had to change the signature of the ^ function a little to allow other key-pathes to be disambiguated.

prefix func ^ <Root, Value, Path: KeyPath<Root, Value>>(keyPath: Path) -> Path {
  return keyPath
}

Edit: It worked in playground, but it seems to cause more issues in the project, so I went back to a more specific overload.