KeyPath: Type of expression is ambiguous without more context

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
  }
}

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.

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

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

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

1 Like

I get the same error on 5.1.

1 Like

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.

1 Like

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.

@suyashsrijan any chance you filed a bug report for this that we can track?

Nope! Feel free to file one.. I was trying to investigate why this was happening, but then got sidetracked by other stuff. I'll give this another look when I have some free time, otherwise maybe @xedin can take a look.

Isn't this [SR-5578] Partial key paths don't provide enough type context to omit the base of a key path literal · Issue #48150 · apple/swift · GitHub ?

1 Like

Looks fairly similar to me.