Overloading by return type

Is there a way to get a warning / error for overloading by return type?

struct Test {
    func foo() -> Int { 0 }
    func foo() -> String { "hello" } // warning or error wanted here
}

Hacky, but you could write something like this in a function that's never called:

_ = Test().foo()

Then it would be ambiguous if an overload was introduced.

If it's hard to instantiate a Test, then you could do something like:

let t: Test! = nil
_ = t.foo()

Since you're never going to actually run the code.

1 Like

I actually believed that this was impossible in Swift, as this is almost like a type union - one if the [commonly rejected proposals](swift-evolution/commonly_proposed.md at main · swiftlang/swift-evolution · GitHub).

Do we consider this a bug?

No, overloading on return type is explicitly allowed in Swift, although it's not used very often in the standard library at least.

1 Like

It's kind of hard to disallow due to protocol extensions:

// Module A
public struct S {}

// Module B
import A

public protocol P {}
extension P {
  public func foo() -> Int { 5 }
}

extension S: P {}

// Module C
import A

public protocol Q {}
extension Q {
  public func foo() -> String { "hello" }
}

extension S: Q {}

// Module D
import B
import C

In this code, where would you even put the error?


That being said, I can understand the desire for warnings/errors if this arises within a single module.

In general ambiguous overloads can only be diagnosed at the call site. So if your language did not allow return type overloading but it has enough separate compilation that you can't catch it ahead of time, you would just diagnose any attempt to call such a function from a source location where both overloads were visible.

2 Likes

It is a single module in my case.

Is this something readily available in swift-format / SwiftFormat / SwiftLint or is this too complicated for linters?

Finding overloads the in the same file should be doable by the formatters and linters, since they operate on a syntax level (SwiftLint has been migrating more rules to swift-syntax). If you want detection of all overloads you'd need to compile the whole program, which isn't something formatters and linters really want to do.

That is made difficult by subclassing and protocol extensions though.

1 Like

Disallowing this would be real bad; you'd have to pass in metatypes everywhere instead of having them as a nice option that you never actually use.

let bytes: [UInt8] = [1, 0, 2, 0]

let halfs: [Int16] = [1, 2]
#expect(bytes.load() == halfs)

let floats: [Int32] = [0x2_00_01]
#expect(bytes.load() == floats)
import protocol Foundation.ContiguousBytes

public extension ContiguousBytes {
  func load<Element>(_: [Element].Type = [_].self) -> [Element] {
    withUnsafeBytes {
      $0.withMemoryRebound(to: Element.self, Array.init)
    }
  }
}

Does withUnsafeBytes have special guarantees when applied to ContigousBytes? Because that code wouldn't be safe with any other type. You are never supposed to return the pointer out of withUnsafeBytes. I suppose it might be safe since it's an Objective-C type.

I have occasionally written other functions that use the defaulted metatype trick though.