[swift-evolution] [Completing Generics] Completing protocol extension diagnostics

Howard Lovatt howard.lovatt at gmail.com
Wed Mar 9 21:43:03 CST 2016


For type A problems: why not require that you annotate with overload? In
Java they used to be like Swift; where a method that overrode an interface
declaration, no override was required. But they changed, due to these
problems, to always require override when implementing a previously
declared method, whether this was the first implementation of not. IE:

protocol Channel {
  func receive() -> NSData
}

extension Channel {
  func receive() -> NSData { return NSData() } // Error - no override and
declaration in protocol

  override func receive() -> NSData { return NSData() } // OK - note
override
}

// Silently fails to replace the default implementation:
struct ClumsyChannel: Channel {
  override func recieve() -> NSData { return NSData(bytes: "oops", length:
4) } // Error - Wrong spelling

  override func receive() -> [UInt8] { return Array("whoopsie".utf8) } //
Error - Wrong return type

  override func receive() throws -> NSData { throw "doh" } // Error - Wrong
throwiness

  func receive() -> NSData { return NSData(bytes: "Forgot", length: 6) } //
Error - no override and declaration in protocol

  override func receive() -> NSData { return NSData(bytes: "OK", length: 2) }
// OK - note override
}


This implies that methods implemented in extensions gain dynamic dispatch;
which I believe is under consideration anyway.

As an aside: I think that protocol extensions are only popular because you
can't put bodies in protocols. If bodies are allowed, I would think that
extensions to protocols will become the exception rather than the rule.

  -- Howard.

On 4 March 2016 at 11:08, Joe Groff via swift-evolution <
swift-evolution at swift.org> wrote:

> Under the umbrella of completing generics, I think we should make room for
> improving our diagnostics around protocol extensions. They're a
> well-received feature, but they introduce a lot of surprising behavior, and
> introduce opportunity for subtle bugs. We didn't have time to put much
> diagnostic work in last year, but now that users have had time to work with
> the feature, we have evidence of some of the more surprising and
> problematic behavior. Among the most common things we've seen reported:
>
> A) When a protocol requirement has an extension implementation requirement
> available, we'll silently ignore when a conforming type attempts to conform
> to the protocol but misses, by type or spelling:
>
> protocol Channel {
>   func receive() -> NSData
> }
> extension Channel {
>   func receive() -> NSData { return NSData() }
> }
>
> // Silently fails to replace the default implementation:
> struct ClumsyChannel: Channel {
>   // Wrong spelling
>   func recieve() -> NSData { return NSData(bytes: "oops", length: 4) }
>   // Wrong return type
>   func receive() -> [UInt8] { return Array("whoopsie".utf8) }
>   // Wrong throwiness
>   func receive() throws -> NSData { throw "doh" }
> }
>
> B) Protocol requirements aren't real class members, and can't be
> overridden by subclasses unless the base class satisfies the requirement
> with one of its own methods rather than with a protocol extension method,
> but we silently allow subclasses to shadow:
>
> class BaseChannel: Channel { } // gets default imp from extension
>
> class SubChannel: BaseChannel {
>   // Doesn't 'override' protocol requirement; silently shadows it
>   func receive() -> NSData { return NSData(bytes: "oof", length: 3) }
> }
>
> C) Similarly, protocol extension methods aren't protocol requirements, so
> extension methods that don't match a requirement can't be specialized in a
> way available to generic code, but we silently allow concrete type
> implementations to shadow:
>
> extension Channel {
>   func receiveAsString() -> String {
>     return String(data: receive(), encoding: NSUTF8Encoding)
>   }
> }
>
> struct StringyChannel {
>   func receive() -> NSData { return NSData(bytes: "data", 4) }
>   // Does not affect generic code calling receiveAsString
>   func receiveAsString() -> String { return "string" }
> }
>
> func foo<T: Channel>(chan: T) {
>   print(chan.receiveAsString())
> }
>
> foo(StringyChannel()) // Prints "data"
>
> (B) and (C) could be mitigated by shadowing warnings, and we've also
> floated ideas for making them behave as intended, by implicitly forwarding
> protocol requirements into class methods to address (B) and/or introducing
> dynamic dispatch for protocol extensions to address (C). (A) is a bit
> trickier, since with overloading it's tricky to divine whether a
> declaration was really intended to match another one with a different type
> in isolation. We've discussed a couple approaches to this problem:
>
> - Adopting an explicit 'implements' modifier, in the spirit of 'override',
> to mark a declaration as being intended to fulfill a requirement. This adds
> boilerplate we'd like to avoid, and also interferes with retroactive
> modeling.
> - Encourage "one extension per conformance" style, where each protocol
> conformance's requirements are defined in a dedicated extension. We can
> then warn about any declarations in an extension that don't satisfy a
> requirement:
>
> struct InconsistentChannel {}
>
> extension InconsistentChannel: Channel {
>   func receive() -> NSData { ... } // OK
>   func recieve() -> NSData { ... } // Warning: Declaration in conformance
> extension doesn't satisfy any requirements from 'Channel'
>   func receive() -> NSData? { ... } // Warning
> }
>
> There are likely others too. It'd be great if we could give users better
> guidance about this feature.
>
> -Joe
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160310/ef1ef587/attachment.html>


More information about the swift-evolution mailing list