[swift-users] Protocol extension gotcha... or bug?

Slava Pestov spestov at apple.com
Mon Jan 23 13:56:33 CST 2017


> On Jan 23, 2017, at 7:31 AM, Wagner Truppel via swift-users <swift-users at swift.org> wrote:
> 
> Say you have a protocol and an extension of it, like so:
> 
> protocol P {
>   var item: String { get }
> }
> 
> extension P {
>   var item: String {
>       return "P"
>   }
> }
> 
> Now imagine a class that conforms to that protocol and uses the property declared there but doesn’t specialise it in any way:
> 
> class A: P {
>   var usedItem: String {
>       return item // A uses 'item' but doesn't override or specialise it in any way
>   }
> }
> 
> let a = A()
> a.item         // returns “P"
> a.usedItem // returns “P"
> 
> Not surprisingly, both results equal “P” since A doesn’t specialise 'item'. Now imagine a class B that subclasses A and *does* specialise 'item':
> 
> class B: A {
>   var item: String { // subclass specialises 'item' by "overriding" the protocol extension implementation

This is the problem. You’re not overriding the protocol extension version, you’re just shadowing it for the purposes of compile-time name lookup. Note that if you add the ‘override’ keyword here, the compiler complains because there’s nothing in the base class to override.

At least in the case where the conformance is defined in the same module as the type, we could add new vtable entries to the class to allow the protocol extension method to be overridden in subclasses. But that’s not implemented yet.

Slava

>       return "B"
>   }
> }
> 
> let b = B()
> b.item // returns “B”
> 
> No surprise there either. B specialises 'item' so that’s what gets used. But...
> 
> b.usedItem // returns “P” !!?
> 
> I can hear you thinking "That's because 'usedItem' is being statically dispatched, since it's not declared in the protocol" but adding a declaration for 'usedItem' to the protocol doesn't help! I'll get to that in a moment.
> 
> Now consider class C, which is similar to B but also overrides A’s implementation of 'usedItem' to do, well, exactly what A does (at least syntactically):
> 
> class C: A {
>   var item: String {
>       return "C"
>   }
>   override var usedItem: String {
>       return item
>   }
> }
> 
> let c = C()
> c.item
> c.usedItem
> 
> Now we get “C” for both calls, as desired, but having to add the override keyword just for this is ugly at best. What, then, if we did add a declaration for 'usedItem' to the protocol?
> 
> protocol P {
>   var item: String { get }
>   var usedItem: String { get }
> }
> 
> extension P {
>   var item: String {
>       return "P"
>   }
>   var usedItem: String {
>       return item
>   }
> }
> 
> class A: P {
> }
> 
> let a = A()
> a.item         // returns “P"
> a.usedItem // returns “P"
> 
> No surprises here.
> 
> class B: A {
>   var item: String {
>       return "B"
>   }
> }
> 
> let b = B()
> b.item         // returns “B”
> b.usedItem // still returns “P” !!
> 
> The result is still "P", even though 'usedItem' is now being dynamically dispatched. Yes, B doesn't specialise the protocol extension's implementation of 'usedItem' so there really isn't an implementation of that to dynamically dispatch but =that shouldn't matter= (yet it does). B does specialise 'item', though, so *that* should be dynamically dispatched (since it is declared in the protocol). And it is, if called directly, but if it's called from within the protocol extension's default implementation of another dynamically dispatched invocation that isn't itself specialised, then it's not.
> 
> Of course, there's a solution similar to that of class C but, like that solution, this one also involves the presence of extra code that is syntactially identical to the default implementation.
> 
> class C: A {
>   var item: String {
>       return "C"
>   }
>   var usedItem: String {
>       return item
>   }
> }
> 
> let c = C()
> c.item         // returns “C”
> c.usedItem // now returns “C”, as desired.
> 
> What I find most surprising is that the static or dynamic dispatch behaviour of a method in the protocol extension is not determined solely by the absence or presence of its declaration in the protocol but also by whether or not conforming types actually have a specialised implementation. As I pointed out above, that second bit should not matter, yet it does. It seems to me that this is a bug in how protocol extensions work but I might be wrong about that.
> 
> Any thoughts?
> 
> Thanks.
> Wagner
> _______________________________________________
> swift-users mailing list
> swift-users at swift.org
> https://lists.swift.org/mailman/listinfo/swift-users



More information about the swift-users mailing list