Skip to content

Instantly share code, notes, and snippets.

@KingOfBrian
Forked from erica/overrides.md
Last active June 15, 2017 19:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KingOfBrian/fcec180bfa010b4f03aba4e7e3147027 to your computer and use it in GitHub Desktop.
Save KingOfBrian/fcec180bfa010b4f03aba4e7e3147027 to your computer and use it in GitHub Desktop.
Requiring Proactive Overrides for Default Protocol Implementations

Requiring Proactive Overrides for Default Protocol Implementations

  • Proposal: tbd
  • Author(s): Erica Sadun
  • Status: tbd
  • Review manager: tbd

Introduction

This proposal enhances protocol implementation safety. It incorporates two keywords that cooperate with compiler checks to limit "near miss" implementation errors and accidental member overrides. This is a strictly syntactic system intended to provide greater safety at compile time, and would not affect existing compiled code bases. *** BK: Saying it does not affect implies to me that it's optional. ***

This proposal was discussed on the Swift Evolution list in the [Pitch] Requiring proactive overrides for default protocol implementations. thread

Motivation

The proposal introduces a mandatory required keyword that marks members as fulfiling protocol requirements. This expansion reduces the risk of near-miss implementations (for example, adding thud(x: Double) when thud(x: Float) is required), provides in-line documentation of why the member has been included, thereby enhancing the code-level documentation at the implementation point, and supports compile-time checks for protocol conformance.

This proposal extends the override keyword to protocol conformance. The Swift Programming Language describes the way subclass methods must override implementations established in superclasses. Methods on a subclass that override the superclass’s implementation are marked with override—overriding a method by accident, without override, is detected by the compiler as an error. The compiler also detects methods with override that don’t actually override any method in the superclass.

Adding an override requirement expands this cautious approach to protocols. Developers must override implementations inherited from protocol extensions with the override keyword. And the compiler will flag uses of override where member implementations do not, in fact, override an existing implementation. The keyword prevents accidental overrides, where a sensible member name conflicts with signatures established in the protocol conformance and forces users to proactively select a version in favor of existing protocol extensions. *** BK: This also clarifies behavior of methods that shadow extensions in protocol extensions, since the override keyword is not allowed ***

Detail Design

  • The override keyword is extended to protocol inheritance, and when used prefers the overridden behavior to the default behavior.
  • Swift will prefer an overridden implementation in preference in reverse hierarchical order: type extensions take precedence over type declarations over protocol extensions over protocol declarations (assuming protocol declarations eventually adopt default implementations).
  • The required keyword marks a member as satisfying a protocol requirement, whether in protocol extensions, type declarations, or type extensions.

*** BK: There may still be confusion with the example below. I'm guessing the compiler would be happy here, but it's not clear that foo doesn't override the protocol extension ***

class Bar {
  func foo() {}
  func foo2() {}
}
protocol Baz {
  func foo() {}
}
extension Baz {
  func foo2() {}
}
class BarBar: Baz, Bar {
  override func foo() {} // Does this need `required` too?
  override func foo2() {} 
}

*** End BK Snipit ***

Required Protocol Members

Protocol requirements are marked with required for compile-time checks of intentional conformance.

protocol A { 
    func foo() 
    func bar()
    func blort()
    func gar()
}

extension A {
    default func blort() {} // Correct, required by `A` *** BK: default ***
    func womble() {} // Correct, new method in extension
    func gar() {} // Incorrect: Compiler says: add `default` keyword or remove implementation *** BK: default ***
}

struct B: A {
    required func foo() {} // Correct
    required func far() {} // Near miss. Compiler: rename method or drop required keyword
    func bar() {} // Possible accidental name match. Compiler: rename method or add required keyword
}

Member Overrides

Overrides are marked with override to ensure intent.

protocol A { 
    func foo() 
    func bar()
    func blort()
    func gar()
}

extension A {
    default func foo() {} // correct *** BK: default ***
    func womble() {} // correct
}

struct B: A {
    required func bar() {} // correct
    required func foo() {} // incorrect: Compiler says: add `override` keyword or remove implementation
     func womble() {} // incorrect: Compiler says add `override` keyword or remove implementation. `required` is not needed as `womble` is not a required protocol member.
}

Handling Changes

Default implementations can be added or removed at any time, as can type conformance implementations:

**Original** **Change** **Outcome**
Some member implemented in type annotated with protocol Protocol adds that member Must add `required` to type implementation or rename member to avoid conflict
Some member implemented in type not annotated with protocol, even if separate extension conforms to protocol Protocol adds that member No change
Some member implemented in type, marked as `required` Protocol removes that member or it never existed Must remove `required` from type implementation
Some member implemented in type, marked as `override` Protocol extension removes that member or it never existed Must remove `override` from type implementation
Some member implemented in type, member not mentioned in protocol Extension adds default version of member Type implementation must add `override` keyword
`required` member implemented in type Default member added *** BK: Default implementation for member added to protocol *** Must add `override` or remove type implementation *** BK: I missed that this would result in `override required` ***
`override required` member implemented in type Remove default member Must remove `override` in type implementation
`override required` member implemented in type Remove type member implementation Default implementation now used
Type member uses `required` keyword Protocol removes requirement or never had it Type implementation must remove `required` keyword
Protocol declares required member Extension implements default implementation Extension must add `required` keyword, differentiating default implementations from added behavior
Swift adds default implementations to protocols as well as extensions *** BK: This scenario is a bit confusing. I think this adds default implementations in the protocol declarations and the ability of extensions to override the default implementation which is a bit confusing and a few steps from where we are. It would help me to be more clear about the scenario or remove it if it explores integration with other, non approved and non-proposed behaviors *** Protocol adds default implementation Type implementation must use both `required` and `override` keywords. Protocol extension must use `override` keyword. Order of preference goes: overriden member, overriden extension, protocol default implementation

Multiple Conformance Conflict

Consider the following situation. For the sake of future-proofing, this example includes default protocol implementations although they do not yet exist in Swift.

protocol A { func foo() {...default...} }
protocol B { func foo() {...default...} }
extension A { override required func foo() {...A extension...} } *** BK: This is confusing and I think is related to the last item in the table above. I'm not sure adding support for overriding a default implementation of a protocol here makes sense to be presented here ***
Type CType: A, B {} *** BK: I found `Type` to be confusing. You're using `struct` above, I'd stick with one approach ***

In this example, the compiler emits a warning that "CType cannot unambiguously differentiate which version of foo to use for CType instances". If the CType type were to be removed or either of its conformances erased, there would be no compiler issues.

To fix this scenario, CType must implement a version of foo that resolves the conflict:

Type CType: A, B { override required func foo() { 
    // either
    A.foo(self)() // uses the A extension default implementation
    // or
    B.foo(self)() // uses the B protocol default implementation
    // or both, one after the other, etc.
}

In this rewrite, foo is unambiguously referenced for CType instance members.

Overriding Conformance Defaults with Unowned Code

Consider the following situation:

// Code you do not own
Type DType { func foo() }

// Protocol you may or may not own
protocol A { func foo() }
extension A { func foo () { default implementation } }

// And you conform the imported type
extension DType: A {...something...}

How do you specify that DType instances should prefer or use the native third party implementation in favor of the default implementation? In this case, there must be some way to either assign the requirement:

extension DType: A {
    override required func foo = DType.foo
}

Or refer to the original implementation: *** BK: I think the original behavior would be A.foo(self)() ***

extension DType: A {
    override required func foo() {
        DType.foo(self)()
    }
}

Impact on Existing Code

These changes introduce mandates that do not exist in today's Swift code and will require migration. The migrator (and compiler) must detect both scenarios: that a member satisfies a protocol requirement and needs the required keyword, and that a member overrides a default implementation (in current Swift, only in extensions) and needs the override keyword.

In the degenerate case that protocol extensions provide two distinct default implementations of the same member (whether required or not), the override version should always be preferred. When multiple override versions exist, the compiler should emit a warning about ambiguous resolution. *** BK: I'm a bit confused, isn't providing two distinct default implementations a new feature? Or are you refering to sub-protocoling? ***

Futrue directions *** BK: future ***

Using type-style currying for protocols, e.g. A.foo(self) should resolve conflicts using the rules enumerated earlier in this proposal. The general flow of precedence would move up from type extensions to types to protocol extension to protocols.

Alternatives Considered

Not at this time.

Acknowledgements and Thanks

Thanks, Doug Gregor, Jordan Rose, and Joe Groff

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment