Warning when "overriding" an extension method that's not in the protocol?


(Chris Eidhof) #1

Let's consider some subtle behavior when dealing with protocol extensions. We have a simple protocol:

protocol Shareable {
    var myDescription: String { get }
}

Using an extension, we can add a quick function `share` that prints the social media description to the command line:

extension Shareable {
    func share() {
        print("Sharing: \(self.myDescription)")
    }

    func linesAndShare() {
      print("----------")
      share()
      print("----------")
    }
}

We can make string conform to `Shareable` by implementing the `myDescription`. This is all we need to do, we then automatically get the `share()` method for free. However, if we choose to, we can also create a custom variant of the `share` method. For example, if we make `String` conform to `Shareable`, we could do it like this:

extension String: Shareable {
    var myDescription: String { return self }
    
    func share() {
        print("Special String Sharing: \(self.myDescription)")
    }
}

Now, if we create a string and call `share()` on it, it will use our custom `share` method:

"hello".share()
// Prints "Special String Sharing: hello"

However, if we treat "hello" as a `Shareable` value, and then call `share()`, we get a very different result:

let hello: Shareable = "hello"
hello.share()
// Prints: "Sharing: hello"

Things get even more interesting. What happens if we call `linesAndShare` directly on a `String`?

"hello".linesAndShare()
// Prints:
//
// ----------
// Sharing: hello
// ----------

Coming from dynamic languages, you might be very surprised by the output of the last two examples. Even though we defined `share` on `String`, it did not override our default implementation. If we want to allow types conforming to `Share` to provide their own custom implementations, we need to specify `share()` in the protocol as well:

protocol Shareable {
    var myDescription: String { get }
    func share()
}

Now, we can still provide a default implementation of `share()`, yet allow other types to override the `share()` function.

If you've made it all the way through, I want to propose that we add a warning in case you override a protocol extension method that's visible in your scope (and has exactly the same type). For example, implementing `share()` in String could trigger a warning. In my experience, it's confusing that it looks like you're overriding it, but you're not really. I strongly believe the current behavior is correct, yet, it can be quite confusing, especially coming from ObjC.

Chris


(Brent Royal-Gordon) #2

If you've made it all the way through, I want to propose that we add a warning in case you override a protocol extension method that's visible in your scope (and has exactly the same type). For example, implementing `share()` in String could trigger a warning. In my experience, it's confusing that it looks like you're overriding it, but you're not really. I strongly believe the current behavior is correct, yet, it can be quite confusing, especially coming from ObjC.

We’re actually discussing this for the last few days in the threads "Proposal: Require explicit modifier for statically dispatched extension methods” (newer) and "Proposal: Universal dynamic dispatch for method calls” (older). You might want to dig into the list archives and take a look at them. <https://lists.swift.org/pipermail/swift-evolution/>

···

--
Brent Royal-Gordon
Architechies


(Douglas Gregor) #3

FWIW, I consider adding a warning for this case to be a bug fix [*] that doesn’t need to go through the evolution process: it’s an area where users clearly get surprised, and even if we do end up making changes in Swift 3.0, we could help users of Swift 2 greatly in the interim.

  - Doug

[*] Not coincidentally, a bug has been on me for a long time to do this… sorry.

···

On Dec 10, 2015, at 3:13 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

If you've made it all the way through, I want to propose that we add a warning in case you override a protocol extension method that's visible in your scope (and has exactly the same type). For example, implementing `share()` in String could trigger a warning. In my experience, it's confusing that it looks like you're overriding it, but you're not really. I strongly believe the current behavior is correct, yet, it can be quite confusing, especially coming from ObjC.

We’re actually discussing this for the last few days in the threads "Proposal: Require explicit modifier for statically dispatched extension methods” (newer) and "Proposal: Universal dynamic dispatch for method calls” (older). You might want to dig into the list archives and take a look at them. <https://lists.swift.org/pipermail/swift-evolution/>