11

I am trying to migrate my code from xcode 8.2 swift 3.0.2 to xcode 9 swift 4, and I have problem with this code:

func test<T0, TRet>(_ fn: (T0) -> TRet) -> Void {
    print("foo1")
    print(T0.self)
}

func test<T0, T1, TRet>(_ fn: (T0, T1) -> TRet) -> Void {
    print("foo2")
    print(T0.self)
    print(T1.self)
}

let fn2 : (Int, Int) -> Int = { (x:Int, y:Int)->Int in
    return x+y
}

test(fn2)

xcode 8.0.2, swift 3.0.2 results with:

foo2
Int
Int

xcode 9, swift 4 results with:

Playground execution failed:

error: MyPlayground.playground:12:1: error: ambiguous use of 'test'
test(fn2)
^

MyPlayground.playground:1:6: note: found this candidate
func test<T0, T1, TRet>(_ fn: (T0, T1) -> TRet) -> Void {
^

Am I missing something? Is there any new feature in swift 4 that causes this error?


Update

I filed a bug at bugs.swift.org as suggested in the comments.
https://bugs.swift.org/browse/SR-6108

Community
  • 1
  • 1
tal952
  • 953
  • 12
  • 18
  • Interesting; if you comment out the second overload of `test`, Swift will call the first overload by converting the function from `(Int, Int) -> Int` to `((Int, Int)) -> Int`. However, this should be forbidden, as [implicit tuple splatting was removed from the language](https://github.com/apple/swift-evolution/blob/master/proposals/0029-remove-implicit-tuple-splat.md), therefore I'd recommend you file a bug. – Hamish Oct 09 '17 at 14:48
  • Function arguments can take tuples. Therefore the compiler can make T0's type (Int, Int). Isn't tuple splat passing a single tuple value into a multi-argument function? The question now is why does the compiler not pick the more strigent two argument test function over the single. – Price Ringo Oct 09 '17 at 15:11
  • It may be related to [SE-110](https://github.com/apple/swift-evolution/blob/master/proposals/0110-distingish-single-tuple-arg.md), but (a) that proposal, though accepted at first, was later [reverted](https://lists.swift.org/pipermail/swift-evolution-announce/2017-June/000386.html), and (b) if anything, I'd expect your code to be ambiguous before SE-110 and unambiguous after it, i.e. the other way around from what you're observing. Definitely worth reporting at https://bugs.swift.org in my opinion. – Ole Begemann Oct 09 '17 at 15:56
  • Agreed. However you can resolve the ambiguity with different parameter labels in your test functions instead of _. – Price Ringo Oct 09 '17 at 15:59

1 Answers1

2

I ran into the same problem, and stumbled across a workaround that is (for my purposes) nicer than disambiguating via naming. Perhaps it is not even a workaround, just the way things have to be. It's also possible that this is newly-possible in Swift 4.1 (not sure, since I migrated directly from Swift 3 to 4.1)

Change this:

func test<T0, TRet>( fn: (T0) -> TRet) -> Void

...to this...

func test<T0, TRet>( fn: ((T0)) -> TRet) -> Void

(note the extra pair of parens around the T0 callback parameter, which explicitly makes it into a tuple-of-1)

After this change, test(fn2) compiles and calls the test<T0,T1,TRet> overload. It seems that the compiler is able to treat a function with N arguments as a function with one N-way-tuple argument. Hence, both the (T0) -> TRet and (T0,T1) -> TRet overloads are candidates for fn2, and the call is ambiguous. Adding 2nd pair of parens ((T0)) -> TRet limits that overload to an argument with a single parameter or 1-way tuple.

jlew
  • 10,491
  • 1
  • 35
  • 58
  • 1
    In Swift 5, you need extra parenthesis on all overloads, i.e., it becomes `func test(_ fn: ((T0, T1)) -> TRet) -> Void` – Liteye Feb 21 '20 at 07:17