[swift-evolution] Proposal: Universal dynamic dispatch for method calls

Kevin Ballard kevin at sb.org
Tue Dec 8 16:53:20 CST 2015


How does this handle the case where the extension was not done in the
same module that defined the protocol, and the extension method's name
is the same as a method on one of its implementing types (from the same
module, or another module that can see the protocol but not the
extension)? Calling the method via the protocol cannot possibly call the
implementing type's version, it must call the default implementation in
the extension, for two reasons:

1. The module that defined the type doesn't know about the extension, so
the method on the type was written without any knowledge of the
extension's method and may not match the expected behavior for the
protocol method.
2. There's no protocol witness table that can contain the extension
method for that type, so even if #1 was irrelevant, there'd be no way to
actually call it. The module that defined the type has no knowledge of
the protocol extension, so it can't construct a protocol witness table.
And the module that defined the extension doesn't know about the type
(since it's in a different module).

The only way this could work is if the extension is in the same module
as the protocol definition, or if the type that conforms to the protocol
can also see the extension.

These same problems occur with an opt-in `dynamic` keyword for protocol
extension methods, of course, but at least there the method is
explicitly called-out as being special and so it's a little more
reasonable to understand that it will only be overridable by types that
can see the extension.

-Kevin Ballard

On Tue, Dec 8, 2015, at 01:51 PM, Brent Royal-Gordon wrote:
> >> It seems
> >> to me that, this case would be better served by some mechanism that makes
> >> the non-override intent explicit at the point of declaration.
> > 
> > There is already such a mechanism. It's the fact that the declaration
> > happens in an extension instead of in the original protocol.
> 
> No, what you’re saying is that there’s a simple rule governing which
> declarations are dispatched statically. This does not make it *explicit*.
> In the Swift 1 betas, there was a simple rule governing whether `if foo`
> was a nil check (is `foo` of type Optional?), but this was deemed
> insufficiently explicit and removed. Making the protocol extension
> behaivor *explicit* would mean, for instance, forcing the user to mark
> statically-dispatched protocol extension methods `final`, much like how
> you have to use `override` purely to reassure the compiler that you mean
> to do what your code says.
> 
> Given the tenor of the rest of the language, here’s how I would design
> this:
> 
> 1. You can mark a protocol extension member as `final`. This means that,
> when casted to the protocol’s type, all instances will use *this*
> implementation, so it can safely be statically dispatched.
> 
> 	protocol Foo {
> 		func bar()
> 	}
> 	extension Foo {
> 		final func baz() { … }
> 	}
> 
> 2. Currently, all protocol extension members which aren’t listed in the
> protocol itself must be final:
> 
> 	protocol Foo {
> 		func bar()
> 	}
> 	extension Foo {
> 		func baz() { … }	// error: declaration must be ‘final’.
> 	}
> 
> If a future version of the language were to allow overriding of extension
> methods, this rule would go away, and use of final would be purely
> voluntary.
> 
> 3. Attempting to make a type conform to a protocol that has `final`
> members conflicting with its own causes an error:
> 
> 	class ConcreteFoo {
> 		func baz() { … }
> 	}
> 	extension ConcreteFoo: Foo {	// error: Foo.baz() conflicts with ConcreteFoo.baz()
> 		func bar() { ... }
> 	}
> 
> 4. The preferred way to handle these conflicts is to rename one of the
> members so they no longer conflict. However, if that isn’t possible, you
> can use a keyword to acknowledge and silence the error, either at the
> site of the conformance (preferred):
> 
> 	extension ConcreteFoo: @incoherent Foo {
> 
> Or at the site of the extension (as a fallback for when someone else
> wrote the conformance):
> 
> 	@incoherent(ConcreteFoo) extension Foo {
> 		final func baz() { … }
> 	}
> 
> It may also be necessary to allow it at the site of an import:
> 
> 	import FooKit
> 	import ConcreteFooKit
> 	@incoherent(ConcreteFoo: Foo) import FooExtensions
> 
> I like “incoherent” because, to me, it expresses the symptom you’re going
> to see (viewing the object as a Foo will not give you the same members as
> viewing it as a ConcreteFoo). But @conflicting would be more obvious, and
> of course we could bikeshed this endlessly, in what appears to be the
> manner of our people.
> 
> -- 
> Brent Royal-Gordon
> Architechies
> 


More information about the swift-evolution mailing list