Concerned about this protocol extension/conformance bug

I raised an issue a few weeks ago, whereby the compiler allows classes to define properties with types that different from the types declared in protocols to which they conform, and where a protocol extension has been provided for the original protocol’s property.

I am really concerned that this bug is not getting any traction, as I’m doing a lot of soon-to-be-public work on “conventions” using protocol extensions and this bug leads to very confusing problems for developers:

https://bugs.swift.org/browse/SR-6689

Is there any way to make sure this issue is triaged and considered for fixing soon because it looks minor on the surface but in practice makes it really problematic to make frameworks or reusable code that use protocol extensions to provide property defaults.

1 Like

The question of whether this is a bug seems complex, given that Swift generally allows you to both shadow things and overload on return type. Then, on top of this, there’s also retroactive conformance and retroactive protocol extensions (and other protocol evolutions) to consider, where a protocol gains a property that may conflict with any of the types that conform to it. It’s probably worth a warning at least, like the “near miss” warning for protocol methods.

As @jawbroken writes, being permitted to shadow variables is not itself a bug because retroactive conformance and other advanced features are intentionally supported features that require such behavior.

By the way, you can shadow protocol extension computed properties with an identically named property even of the same type on concrete types:

protocol P { }

extension P {
  var x: Int { return 42 }
}

struct S : P {
  var x: Int = 21
}

let s = S()
print(s.x) // 21
print((s as P).x) // 42

It’s reasonable to ask for diagnostics, however, and they’ve now been implemented in Swift. There are now warnings for near-misses in extensions that state conformance. In other words, extension Foo: Bar will cause the compiler to warn you if you write something that’s almost a requirement of Bar within that particular extension. This serves as an extra “carrot” in encouraging developers to adopt the style where they implement conformances in separate extensions. It also happens to allow Swift to continue to support advanced features such as retroactive conformance while giving developers a way to catch near-misses.

So, in summary: if you want the compiler’s help, that help already exists, but you must implement conformances of types to protocols in separate extensions to receive it.

3 Likes

@xwu - my specific complaint is pretty different however. The protocol defines the property and the extension provides conformance to that. I do understand Swift has other features around this, but we shouldn’t have a situation where people are trying to do the right thing (conform to something), rather than trying to be smart.

A warning would be great if that’s all that is possible. The problem really is pernicious because unless you are a hardcore Swift dev interested in the nuances of compilation etc. (which people should not have to be - and I barely want to know about these issues to be honest!), the errors will not make any sense as you look at your code and it looks like you have done the right thing. The inferred property type is a particularly bad one, as we are encouraged to use Swift type inference but if you do, and the protocol’s property is optional you will often get the wrong type and weird compiler errors at other callsites.

protocol P {
   var title: String?
}
extension P {
   var title: String? { return nil }
}
class X: P {
   var title = "Ouch" // This declares the property as String, not String?
}

This is the worst case I keep hitting - remember that this is a lot more confusing when P and its extension are defined in another framework you did not write yourself.

I would love for people to have a clear message about what is probably wrong when this happens.

It’s worth noting that this is a feature request, rather than a bug report. Swift’s overload resolution and shadowing is behaving in a well-defined way here, even if it is easily misunderstood. As such, this is more a question for the evolution forum than a JIRA.

That said, I think it’s definitely something that’s worth addressing, as it is a gotcha. There are two ways to handle this:

  1. This could be covered by the near-miss warnings that @Douglas_Gregor added recently. The problem here is that one person’s useful near-miss warning is another’s infuriating compiler noise. I think Doug has some ideas to resolve this by only warning under more specific circumstances, but it’s not clear if this case would be covered.

  2. We disallow the current ability to infer the type of a stored property from a default. That is, you would have to write:

struct S {
  // must declare type of p explicitly
  let p: String? = “foo”
}

This would match our policy on requiring the type for a function argument that has a default value. The reason for that as I understand it is that functions are contracts and should be explicit. Really, stored properties are the same (at least public ones). And it’s not like you can omit the type on a computed property. Requiring stored properties to always be explicit would also have performance benefits for the type checker.

But there would be significant opposition to this too I expect, from users who like the brevity of not having to specify the type. And you can still mis-declare the type, it’s just it will cause you to think about it a little harder.

So, I think both solutions in combination would be appropriate, but neither are without controversy. It may be worth bringing this up on the evolution for more discussion if you think these are important features to consider.

1 Like

Thanks Ben - this week I’ll draft something and take it to Swift-evolution

FYI here's the evolution post: