Ambiguous use of 'remove(at:)' for Array.remove

Background

I was tweaking some S*UI code to fix a bug. But after I delete one opacities.remove(at: 2) code. The compiler just give me an error.

To reproduce it, we have to nest a GCD dispatch with a function like withAnimation and then only use a single Array.remove method.

MInimal reproduce code

import Dispatch
func withAnimation<Result>(_ body: () throws -> Result) rethrows -> Result {
    try body()
}

func test() {
    var opacities: [Double] = [0, 0.5, 1.0]
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        withAnimation {
            opacities.remove(at: 2) // โŒ Ambiguous use of 'remove(at:)'
        }
    }
}

The full compiler error is actually very confusing for me.

โžœ  DemoKit git:(main) swift build
Building for debugging...
/Users/kyle/Downloads/DemoKit/Sources/DemoKit/DemoKit.swift:14:23: error: ambiguous use of 'remove(at:)'
12 |     DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
13 |         withAnimation {
14 |             opacities.remove(at: 2) // โŒ Ambiguous use of 'remove(at:)'
   |                       `- error: ambiguous use of 'remove(at:)'
15 |         }
16 |     }

Swift.Array.remove:3:35: note: found this candidate in module 'Swift'
1 | generic struct Array {
2 | @discardableResult
3 |   @inlinable public mutating func remove(at index: Int) -> Element}
  |                                   `- note: found this candidate in module 'Swift'
4 |

Swift.RangeReplaceableCollection.remove:3:35: note: found this candidate in module 'Swift'
1 | protocol RangeReplaceableCollection {
2 | @discardableResult
3 |   @inlinable public mutating func remove(at position: Self.Index) -> Self.Element}
  |                                   `- note: found this candidate in module 'Swift'
4 |

Workaround

It turns out to be a Swift compiler bug for me.

And the workaround is use _ = opacities.remove(at: 2) or use multiple statements here.

import Dispatch
func withAnimation<Result>(_ body: () throws -> Result) rethrows -> Result {
    try body()
}

func test() {
    var opacities: [Double] = [0, 0.5, 1.0]
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        withAnimation {
            opacities.remove(at: 2) // โœ…
            opacities.remove(at: 2) // โœ…
        }
    }
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        withAnimation {
            _ = opacities.remove(at: 2) // โœ…
        }
    }
}

GH issue: Ambiguous use of 'remove(at:)' error when using Array.remove in nested closure with @discardableResult ยท Issue #86233 ยท swiftlang/swift ยท GitHub

3 Likes

How could both of these compile?

@frozen public struct Array<Element> {
    @discardableResult
    @inlinable public mutating func remove(at position: Int) -> Element
extension Array : RangeReplaceableCollection {
    @discardableResult
    @inlinable public mutating func remove(at index: Int) -> Element

If I try to do something like this with my own types I am getting an error right away:

Invalid redeclaration of 'remove(at:)'

I can see there only exist one implementation for Array in std.
I suspect the diagnostic we got is not accurate here.

extension Array : RangeReplaceableCollection {
    @discardableResult
    @inlinable public mutating func remove(at index: Int) -> Element
}

Actually, in the standard library, the setting is a little bit different, one of the methods is defined on Array and one on RangeReplaceableCollection. Like this:

protocol MyRangeReplaceableCollection {
    associatedtype Index
    associatedtype Element
}

extension MyRangeReplaceableCollection {
    mutating func remove(at position: Index) -> Element {
        fatalError()
    }
}

struct MyArray<Element>: MyRangeReplaceableCollection {
    typealias Index = Int
    mutating func remove(at position: Int) -> Element {
        fatalError()
    }
}

The above code compiles fine, but when remove is invoked, an error will occur on the caller's side:

import Dispatch
func withAnimation<Result>(_ body: () throws -> Result) rethrows -> Result {
    try body()
}

func test() {
    var opacities: MyArray<Int> = .init()
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        withAnimation {
            opacities.remove(at: 2)  // same error as in OP
        }
    }
}
1 Like

Distilling it a bit further:

protocol MyRangeReplaceableCollection {}

extension MyRangeReplaceableCollection {
    func remove() -> Int { 42 }
}

struct MyArray: MyRangeReplaceableCollection {
    func remove() -> Int { 21 }
}

func myWithAnimation<T>(body: () -> T) -> T { body() }

func myAsyncAfter(execute: () -> Void) { execute() }

func test() {
    var opacities = MyArray()
    myAsyncAfter {
        myWithAnimation {
            opacities.remove()  // Ambiguous use of 'remove()'
        }
    }
}
1 Like

Similar simpler example:

struct MyArray {
    func remove() -> Int { 21 }
}

func myWithAnimation<T>(body: () -> T) -> T { body() }

func myAsyncAfter(execute: () -> Void) { execute() }

func test() {
    var opacities = MyArray()
    myAsyncAfter { // Type of expression is ambiguous without a type annotation
        myWithAnimation {
            opacities.remove()
        }
    }
}

Changing the inner code to contain explicit return fixes it:

        ...
        myWithAnimation {
            return opacities.remove()
        }
        ...

Yet adding a seemingly unrelated statement breaks it again:

        ...
        myWithAnimation {
            print()
            return opacities.remove() // Cannot convert value of type 'Int' to closure result type 'Void'
        }
        ...

Unrelated to the compilation issue, the code above could be simplified to take advantage of delay modifier:

    withAnimation(.default.delay(1)) {
        ...
    }
1 Like

It seems to be the return value of myWithAnimation that confuses compiler (note myAsyncAfter doesn't return value). The following change fixes it in my experiments.

  func test() {
    var opacities = MyArray()
   myAsyncAfter {
-       myWithAnimation {
+       _ = myWithAnimation {
            opacities.remove()
        }
    }
  }

Further distillery with generics removed:

func anim(body: () -> Int) -> Int { body() }
func anim(body: () -> Void) { body() }

func test1() { anim { 42 } }                    // OK, expected
func test2() { anim { 42.0 } }                  // OK, expected
func test3() { anim { print() } }               // OK, expected
func test4() { anim { print(); print() } }      // โŒ Error, unexpected
func test5() { anim { print(); return 42 } }    // OK, expected
func test6() { anim { print(); 42 } }           // โŒ Error, unexpected (โ€ )

(โ€  optional returns with last expression rule is not yet in the language.)

Same but wrapped in another closure:

func myAsync(execute: () -> Void) { execute() }

func testAsync1() {
    myAsync { anim { 42 } }                     // โŒ Error, unexpected
}
func testAsync2() {
    myAsync { anim { 42.0 } }                   // OK, expected
}
func testAsync3() {
    myAsync { anim { print() } }                // OK, expected
}
func testAsync4() {
    myAsync { anim { print(); print() } }       // OK, expected
}
func testAsync5() {
    myAsync { anim { print(); return 42 } }     // โŒ Error, unexpected
}
func testAsync6() {
    myAsync { anim { print(); 42 } }            // OK, expected
}