Search code examples
arraysswiftswift3swift-extensions

Swift 3.0: compiler error when calling global func min<T>(T,T) in Array or Dictionary extension


After converting from Swift 2.2 to 3.0 my Array extension does not compile anymore, because it contains a call to global standard library function min<T>(T,T) and shows compiler error extra argument in call.

Here's a simple way to reproduce the error:

extension Array {
    func smallestInt(first: Int, second: Int) -> Int {
        return min(first, second) // compiler error: "Extra argument in call"
    }
}

I get the same error when adding the same function to an extension of Dictionary, while the exact same code compiles just fine in an extension of other types (e.g. String or AudioBuffer):

compiler errors in Array and Dictionary extensions

Looking at the documentation of Array and Dictionary, I find that there are instance methods on Sequence named public func min() -> Element? and public func min(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> Element?. While both String and AudioBuffer do not have any kind of min(...) function.

Is it possible that this is the reason why I can't call the global function? The compiler can't distinguish between global func min<T>(T,T) and self.min(...) although they have completely different signatures?

Is this a bug or a feature? What am I doing wrong? How can I call min(T,T) correctly inside an Array extension?


Solution

  • I see no reason why the compiler shouldn't be able to resolve this function call, therefore I would consider it a bug (it has already been filed – see SR-2450).

    It seems to occur whenever attempting to call a top-level function with the same name, but unambiguously different signature to a method or property that's accessible from the same scope in a given type (instance or static).

    An even simpler example would be:

    func foo(_ a: Int) {}
    
    struct Foo {
    
        func foo() {} // or static func foo() {}, var foo = 0, static var foo = 0
    
        func bar() {
            foo(2) // error: argument passed to call that takes no arguments
        }
    }
    

    Until fixed, a simple solution would be to prefix the call with the name of the module in which it resides in order to disambiguate that you're referring to the top-level function, rather than the instance one. For the standard library, that's Swift:

    extension Array {
        func smallestInt(first: Int, second: Int) -> Int {
            return Swift.min(first, second)
        }
    }
    

    In Swift 4, the compiler has a better diagnostic for this error (though the fact that it's still an error is a bug IMO):

    extension Array {
        func smallestInt(first: Int, second: Int) -> Int {
            // Use of 'min' refers to instance method 'min(by:)'
            // rather than global function 'min' in module 'Swift'
            // - Use 'Swift.' to reference the global function in module 'Swift'
            return min(first, second)
        }
    }
    

    Although what's interesting is that the compiler will now also warn on attempting to call a standard library method with the same name as a stdlib top-level function:

    extension Array where Element : Comparable {
    
        func smallest() -> Element? {
            // Use of 'min' treated as a reference to instance method in protocol 'Sequence'
            // - Use 'self.' to silence this warning
            // - Use 'Swift.' to reference the global function
            return min()
        }
    }
    

    In this case, as the warning says, you can silence it by using an explicit self.:

    extension Array where Element : Comparable {
    
        func smallest() -> Element? {
            return self.min()
        }
    }
    

    Although what's really curious about this warning is it doesn't appear to extend to non-stdlib defined functions:

    func foo(_ a: Int) {}
    
    struct Foo {
    
        func foo() {}
    
        func bar() {
            foo() // no warning...
        }
    }