Even more problems with Swift’s type system: why is Void-returning overload not being preferred here?

i have two overloads called _select(_:where:) shaped like:

extension Sequence 
{
    func _select<Key>(_ key:Key, where predicate:(Int) throws -> Bool) rethrows 
        -> [Int]
    {
        try self._select(key) { try predicate($0) ? $0 : nil }
    }
    func _select<Key, T>(_ key:Key, where filter:(Int) throws -> T?) rethrows 
        -> [T]
    {
        var selection:[T] = []
        try self._select(key) 
        {
            if let selected:T = try filter($0)
            {
                selection.append(selected)
            }
        } as ()
//       ~~~~~^ 
//  why is this needed?
        return selection
    }
    func _select<Key>(_ key:Key, _ body:(Int) throws -> ()) rethrows 
    {
    }
}

for some reason the call to _select(_:_:) inside the second _select(_:where:) method can’t choose between _select(_:_:) and the Bool-predicated _select(_:where:) overload, even though _select(_:_:) is the only choice that returns the expected Void return type.

it works when i explicitly coerce the result to as (). why is this needed?

Simplified your example a bit:

extension Sequence {
    func a(where predicate: (Int) throws -> Bool) rethrows -> [Int] { [1, 2, 3] }
    func a(_ body: (Int) throws -> ()) rethrows { }

    func b(where filter: (Int) throws -> Int) rethrows -> [Int] {
        try a {
            let selected = try filter($0)
        }
        return [1, 2, 3]
    }
}
1 Like

The Void type is not expected there, though. A statement expression discards its result, it does not require that the expression return Void. Perhaps it would be better to give the overloads different names or argument labels instead?

2 Likes

but it is not @discardableResult? surely when given the choice between an overload that generates a warning and an overload that wouldn’t, the compiler ought to prefer the latter?

Perhaps, but philosophically, overload ranking rules are a bit like implicit conversions. They can interact in surprising and counter-intuitive ways, no matter how many you add there is always the temptation to introduce more, and they make the language and implementation harder to understand in the long run.

7 Likes