Are we allowed to overload a property or not?

i had always assumed we cannot overload a property because this is a compiler error:

public
struct S
{
    public
    var readConcern:Int { 1 }
    public
    var readConcern:Int? { 1 }
}
// error: invalid redeclaration of 'readConcern'

and yet i am able to provide overloads in a generic context, and access those overloads in a concretely-typed context:

public
protocol SessionCommand
{
    var readConcern:Int? { get }
}
public
protocol ReadCommand:SessionCommand
{
    var readConcern:Int { get }
}
extension ReadCommand
{
    public
    var readConcern:Int?
    {
        .some(self.readConcern)
    }
}

public
struct S:ReadCommand
{
    public
    var readConcern:Int { 1 }
}
func test(s:S) -> Int?
{
    if let r:Int = s.readConcern
    {
        return r
    }
    else
    {
        return s.readConcern
    }
}
1 Like

Assuming overload by return value is a good thing (which is questionable) this behaviour is already strange: I can do it with a function and with a subscript, but not with a variable?

I am sorry but I could not contact you privately, about the code style.

It looks visually and cognitively pretty jarring, compared to this:

public struct S {
    public var readConcern:Int { 1 }
    public var readConcern:Int? { 1 }
}
// error: invalid redeclaration of 'readConcern'

Is there a compelling reason for this, apart from following some brutal coding standards?

I understand that when very long names and signatures are involved breaking the lines make sense, but why do it when the names are very short.

Merry Christmas and a happy new year! :slight_smile:

4 Likes

it makes it easier to add and remove the access modifiers (public, private, etc.) using editor line operations, which i do quite often. also i personally find it easier to scan for the property names if they all begin at n + 4 columns from the base indent of the type. :slight_smile:

1 Like

I've just run into this, too. The following code compiles fine in Swift 5.10:

protocol Fruit {
    var name: String? { get }
}

extension Fruit {
    var name: String? { nil }
}

struct Pear : Fruit {
    var name: Int = 3
}

I would have expected Pear to not conform to Fruit because name is declared as an Int.

Is this expected behavoir?

If while evaluating a conformance we find multiple candidates with the same name as a protocol requirement, we pick the one with the matching type, if possible. So in this case it’s going to be the default implementation in the protocol extension.

2 Likes

Thanks for the explanation.

It's still weird to me that I'm not getting Invalid redeclaration of 'name' in Pear and that this is valid:

var pear = Pear()
let nameAsString: String? = pear.name // nil
let nameAsInt: Int = pear.name // 3

If Swift didn’t allow this, how would it handle the case where either 1) Pear.name exists from the start, but Fruit.name is added in a future release, or 2) Pear gains a retroactive conformance to Fruit?

To answer the question directly—yes, this is the expected behavior.

1 Like

The redeclaration checking rules are meant to reject situations such as duplicate overloads that cannot be distinguished by name or type, but they don’t cover all potentially confusing situations.

2 Likes

Thanks everyone!
I never doubted that there are good reasons why Swift does it like this; it just didn't match my mental model.

My previous assumption:

  • A property has an unambiguous type

Corrected to:

  • A stored property has an unambiguous type (but overloads may exists in the form of computed properties with the same name)

It’s not even about stored properties per se, just that we allow overloading of property declarations when extensions are involved. Stored properties cannot appear in extensions so you cannot have two stored properties with the same name as a consequence of those two behaviors.

Certainly not every behavior of the implementation has a logical explanation :) But in this case I think it’s pretty reasonable.

You can also just have two constrained extensions that declare computed properties with the same name and type. If the generic signatures are distinct it’s not considered to be a redeclaration, even though you might still end up in a situation where some concrete generic arguments satisfy both signatures, at which point it would be diagnosed as an ambiguity at the call site.

1 Like

The second statement is guiding Swift to pick the declaration from the protocol by providing additional contextual type information.

This feature proves quite useful, for example, when you want to specify the type of a generic function's return value.

func peek <T> (_ u: Any) -> T? {
   u as? T
}

Someone with C++-hat firmly on would automatically write this:

let u = peek <Int> (bar ()) // Cannot explicitly specialize a generic function

And they are relieved when they discover that the following actually works.

for _ in 0..<3 {
    if let u: Int = peek (bar ()) {
        print (u)
    }
    
    if let u: String = peek (bar ()) {
        print (u)
    }
    
    if let u: [Int] = peek (bar ()) {
        print (u)
    }
}

func bar () -> Any {
    let u: [Any] = [3, "three", [1, 1, 1] ]
    return u [Int.random(in: 0..<u.count)]
}