Should allow covariance of {get}-only protocol property requirements?

This came up for us today -- in a situation like this:

class Super {}
class Sub : Super {}
protocol P {
    var property: Super {get}
}
struct S : P {
    var property: Sub
}

Currently Swift will error that Type 'S' does not conform to protocol 'P' because the property type is not an exact match. When the protocol only requires {get}, though, it ought to be safe to allow for covariance in the witness, shouldn't it?

In theory, yes (SR-522). In practice, that might result in a dangerous number of behavior changes, where things that previously weren't considered valid witnesses might now be considered a better match.

4 Likes

Thanks for the bug pointer! I did a search and failed to find it.

1 Like

I've run into the same issue a few times, but it's possible to work around it by using an associatedtype:

class Super {}
class Sub: Super {}
protocol P {
  associatedtype S: Super
  var property: S { get }
}
struct S: P {
  var property: Sub
}
5 Likes

Is the danger @jrose mentioned still inherent in this implementation? I don’t know, because I didn’t understand it in the first place.

I really do not see what those behavior changes might be. @jrose do you have any exemples?
I feel like this feature would be awesome, and already see a ton of usages for it.

protocol P {
  func foo(_ x: String)
}

extension P {
  func foo(_ x: String) { print("default \(x)") }
}

struct S : P {
  func foo(_ x: String?) { print("concrete \(x ?? "")") }
}

(S() as P).foo("abc") // currently prints "default abc"

I'm not saying these behavior changes are necessarily bad ones. But it's definitely a potentially silent change in the meaning of existing code, and that's something that would probably need a new language mode (-swift-version 6 or whatever).

To be clear: I was only proposing covariance for {get} properties, not for all witnesses (i.e. not for functions at all). Since you can't have multiple declarations of properties that differ only by type, I don't think that this could actually do anything unexpected for existing code.

The same example structure works for properties too. :-/ The multiple declarations are on different types.

SR-522 showed that plenty of people want this for functions too, so I don't think the core team would limit it.

Ah, ok, I see what you are saying...

protocol P {
    var foo : String? {get}
}
extension P {
    var foo : String? { "default" }
}
struct S : P {
    var foo : String { "concrete" }
}

S().foo // "concrete"
(S() as P).foo // currently "default", would be "concrete" with this change

Seems really edge-case-y to me, but I'm not the one who has to deal with complaints, I guess. :slight_smile:

(EDIT: Yep, I got the example backwards...)