Why can't computed properties be overloaded (like methods can)?

This compiles (Swift 5.2, Xcode 11.4):

struct S1 {
    static func foo() -> Int { 123 }
    static func foo() -> Bool { true }
    func foo() -> Int { 123 }
    func foo() -> Bool { true }
}
func foo() -> Int { 123 }
func foo() -> Bool { true }

But this doesn't:

struct S2 {
    static var foo: Int { 123 }
    static var foo: Bool { true } // ERROR: Invalid redeclaration of 'foo'
    var foo: Int { 123 }
    var foo: Bool { true } // ERROR: Invalid redeclaration of 'foo'
}

Why?

1 Like

It seems like that problem is a subset of the larger problem: you can't have multiple of anything that's parameterless, unless one is static and one is instance.

struct S<Whatever> {
  // You get only one of these…
  static func foo() { }
  static var foo: Whatever { fatalError() }

  // …and only one of these!
  func foo() { }
  var foo: Whatever { fatalError() }
}

I don't know for a fact, but I suspect that there was no concrete use case for it. Even with method overloading, the fact that you can overload method types based purely on return types (as opposed to parameter types) is a bit spooky, because it leads to code where types can change easily without one noticing. I know some people consider allowing overloading based purely on return types a design oversight/mistake.

1 Like

That's not exactly true. As shown in the OP, the following multiple parameterless things (with or without static) compiles:

struct S {
    static func foo() { } 
    static func foo() -> Int { 123 } // Compiles.
    static func foo() -> Bool { true } // Compiles.
    static func foo() -> Float { 1.23 } // Compiles.
}

Changing those to computed properties does not compile (as shown in the OP), but there actually are scenarios where computed properties can be overloaded too:

struct S<A> {
    static var foo: Int { 123 }
    // static var foo: Bool { true } // Does not compile (as expected).
}
extension S where A == UInt16 {
    static var foo: Bool { true } // Compiles, but should it?
}
extension S where A == UInt32 {
    static var foo: String { "abc" } // Compiles, but should it?
}
extension S where A: UnsignedInteger {
    static var foo: Double { 1.23 } // Compiles, but should it?
}
extension S where A: FixedWidthInteger {
    static var foo: Double { 3.21 } // Compiles, but should it?
}

func bar1<T: UnsignedInteger>(_: T.Type) -> Double { return S<T>.foo }
func bar2<T: FixedWidthInteger>(_: T.Type) -> Double { return S<T>.foo }

func test() {
    print(S<UInt16>.foo as Int)    // 123
    print(S<UInt16>.foo as Bool)   // true
    print(S<UInt32>.foo as String) // abc
    print(bar1(UInt16.self))       // 1.23
    print(bar2(UInt16.self))       // 3.21
}
test()
2 Likes

Conditional and regular overloads share the same ambiguities and are effectively two sides of the same coin. With the introduction of conditional extensions, though, overloads that differ only in their generic signatures started to make a lot more sense, and at the current stage of language evolution, allowing the latter and banning Jens' example would only make the language less consistent and the compiler less uniform – actually, it had already become less consistent, since properties do support conditional overloads just like methods, and yet at some point we seem to had decided to ban the example at hand.

Edit: @Varun_Gandhi Sorry, this was not meant to be a reply to your post specifically.

1 Like

@Jessy, I think I get what you mean by this now:

If by parameterless you mean essentially properties (computed or stored).

Because while this compiles:

func foo() -> Int { 123 }
func foo() -> Bool { true }

This doesn't:

func foo() -> Int { 123 }
func foo() -> Bool { true }
let foo = "abc" // ERROR: Invalid redeclaration of 'foo'

and more generally, no property (computed or stored) can be overloaded, eg:

var foo: Int { 123 }
var foo: Bool { true } // ERROR: Invalid redeclaration of 'foo'
let foo = { (string: String) -> String in string }
let foo = { (_ a: Int, _ b: Int) -> Int in a + b } // ERROR: Invalid redeclaration of 'foo'

with the notable exception of computed properties defined in conditional extensions that I mentioned above.

Sorry, I did not word things well. What I meant by "anything" is a set of member types.

struct TypeDefinition {
  enum Ownership: CaseIterable {
    case `static`
    case instance
  }

  enum Member: Hashable {
    case `func`(Ownership)
    case `var`(Ownership)

    var ownership: Ownership {
      switch self {
      case .func(let ownership):
        return ownership
      case .var(let ownership):
        return ownership
      }
    }
  }

  init(parameterlessMembersWithTheSameName: Set<Member>) throws {
    guard (
      Ownership.allCases.allSatisfy { ownership in
        parameterlessMembersWithTheSameName
          .filter { $0.ownership == ownership }
          .count
          <= 1
      }
    ) else {
      struct TooManyError: Error { }
      throw TooManyError()
    }

    self.parameterlessMembersWithTheSameName = parameterlessMembersWithTheSameName
  }

  let parameterlessMembersWithTheSameName: Set<Member>

  var hasMultipleOfAnythingThatIsParameterless: Bool {
    parameterlessMembersWithTheSameName.count > 1
  }
}

As for the other overloads you mentioned, I like having support for them. It's this form I'm not so sure about…

struct S<A> {
  static var foo: Int { 123 }
}

extension S where A: FixedWidthInteger {
  static var foo: Int { 321 }
}

func bar<T: FixedWidthInteger>(_: T.Type) -> Int {
  S<T>.foo  // 321.
  // Can we get access to 123 somehow?
}
S<Any>.foo // 123
1 Like

Good job. I was in the middle of a puzzle-art-scavenger hunt. Please disregard. :upside_down_face:

What about this one though?

struct S<A> {
  var foo = 123
}

extension S where A: FixedWidthInteger {
  var foo: Int { 321 }
}

var voidS = S<()>()
voidS.foo = 0
voidS.foo // 0

var intS = S<Int>()
intS.foo = 0
intS.foo  // 321. How can we get the zero?

I don't know off the top of my head if there is a better solution, but you can use Mirror

struct S<A> {
  var foo = 123
}

extension S where A: FixedWidthInteger {
  var foo: Int { 321 }
}

var intS = S<Int>()
intS.foo = 0

Mirror(reflecting: intS).descendant("foo")! // 0

(Aside: I agree that there should be a better way to reference this than having to go through reflection.)

Another answer:

struct S<A> {
    var foo = 123
}

extension S where A: FixedWidthInteger {
    var foo: Int { 321 }
}

var intS = S<Int>()
intS.foo = 0
print(intS.foo) // 321. How can we get the zero?

func f<T: Any>(_ s: S<T>) -> Int { s.foo } // <-- Like this.
print(f(intS)) // 0
2 Likes

Assuming this behaves as intended, ie it is not caused by an accepts-invalid bug:

struct S<A> {
  var foo = 123
}

extension S where A: FixedWidthInteger {
  var foo: Int { 321 } // Compiles, but should perhaps be
                       // an invalid redeclaration?
}

Then why shouldn't this compile:

struct S<A> {
    var foo = 123
}

extension S where A: FixedWidthInteger {
    func foo() -> Int { 321 } // ERROR: Invalid redeclaration of 'foo()'
}

?

If there's no reason why that shouldn't compile, then why shouldn't this compile:

struct S {
    var foo = 123
    func foo() -> Int { 321 } // ERROR: Invalid redeclaration of 'foo()'
}

?

cc @Douglas_Gregor

1 Like