[swift-evolution] [Pitch] Overridable Members in Extensions

Andrew Bennett cacoyi at gmail.com
Thu Feb 18 07:13:51 CST 2016


Sounds interesting, I know I've wanted to do similar, and had some weird
bugs.

Many of my concerns have been discussed already, I've added a few more
below:

I'm also a little concerned about the potential for unexpected side-effects
from methods called by the original class, *this* seems to break the
principle of "least astonishment", you're less likely to know the base
classes implementation than that of your extension.

Other points worth considering:

   - Can methods be marked as final, or something else if it's unsafe to
   override?
   - What about methods marked as inline, called by the super-class, is
   this unexpected?
   - *If* most people only want this for testing should it only be allowed
   on classes imported with @testable (only restrict this with (unsafe)
   ObjC classes?).
   - You probably want to swap/replace the implementation rather than just
   override it for testing.
   - I'm presuming that this will not allow you to swap out one
   implementation for another? If not then it may be necessary to be able to
   call or refer to the original version of the method.



On Thu, Feb 18, 2016 at 7:07 PM, Marco Masser via swift-evolution <
swift-evolution at swift.org> wrote:

> +1 because I think this is a good thing in terms of consistency and the
> principle of least astonishment
>
> The introduction states in the note that in the same module, this is
> planned to work anyways, so if I understand correctly, there are two groups
> of people who are impacted by the same thing:
> - Library/framework authors can freely decide to split up their classes
> into separate extensions without worrying about impacting clients of that
> library/framework.
> - Library/framework clients aren’t affected by the author’s choice of
> splitting up their classes into separate extensions.
>
> It would really be unfortunate if I couldn’t override a specific method of
> NSButton just because the App Kit team implemented that method in an
> extension instead of the class proper.
>
> I tend to split up my classes in a couple of extensions that each group
> members that somehow belong together, e.g. a separate extension for all
> @IBActions, one extension implementing conformance to a protocol, one
> private extension with methods dealing with X, etc.
> Right now, I always tend to write all methods that override something into
> the class itself, not an extension because I know there are weird issues
> you can run into (e.g. https://bugs.swift.org/browse/SR-584). I’m looking
> forward to when these will be fixed for classes in the same target (as
> indicated by the note in the introduction), but when the proposal in
> question gets implemented, this would also work as expected for classes in
> different modules.
> Therefore: +1 for consistency and least astonishment.
>
> Marco
>
>
> On 2016-02-11, at 02:45, Jordan Rose via swift-evolution <
> swift-evolution at swift.org> wrote:
>
> Hey, everyone. Here's a small feature with ABI implications, ready for
> feedback. In addition to comments on the proposal itself, I'm also
> interested in hearing how often this comes up for people:
>
> - extending a class you don't own
> - to add an overridable method
> - where some of the overriders might be outside the current module
>
> Jordan
>
>
> https://github.com/jrose-apple/swift-evolution/blob/overridable-members-in-extensions/proposals/nnnn-overridable-members-in-extensions.md
>
> ---
>
> Overridable Members in Extensions
>
>    - Proposal: SE-NNNN
>    - Author: Jordan Rose <https://github.com/jrose-apple>
>    - Status: *Awaiting review*
>    - Review manager: TBD
>
>
> <https://github.com/jrose-apple/swift-evolution/tree/overridable-members-in-extensions#introduction>
> Introduction
>
> Today, methods introduced in an extension of a class cannot override or be
> overridden unless the method is (implicitly or explicitly) marked @objc.
> This proposal lifts the blanket restriction while still enforcing safety.
>
> Note: it's already plan-of-record that if the extension is in the same
> module as the class, the methods will be treated as if they were declared
> in the class itself. This proposal only applies to extensions declared in a
> different module.
>
>
> <https://github.com/jrose-apple/swift-evolution/tree/overridable-members-in-extensions#motivation>
> Motivation
>
> This is used to add operations to system or library classes that you can
> customize in your own classes, as seen in the Apple AdaptivePhotos
> <https://developer.apple.com/library/ios/samplecode/AdaptivePhotos/Listings/AdaptiveCode_AdaptiveCode_UIViewController_PhotoContents_swift.html> sample
> code.
>
> extension UIViewController {
>   func containsPhoto(photo: Photo) -> Bool {
>     return false
>   }
> }
>
> class ConversationViewController : UIViewController {
>   // …
>   override func containsPhoto(photo: Photo) -> Bool {
>     return self.conversation.photos.contains(photo)
>   }
> }
>
> Additional motivation: parity with Objective-C. If Objective-C didn't
> allow this, we might not have done it, but right now the answer is "if your
> method is ObjC-compatible, just slap an attribute on it; otherwise you're
> out of luck", which isn't really a sound design choice.
>
> <https://github.com/jrose-apple/swift-evolution/tree/overridable-members-in-extensions#todays-workaround>Today's
> Workaround
>
> If you know every class that needs a custom implementation of a method,
> you can use dynamic casts to get the same effect:
>
> extension UIViewController {
>   final func containsPhoto(photo: Photo) -> Bool {
>     switch self {
>     case is ListTableViewController:
>       return true
>     case let cvc as ConversationViewController:
>       return cvc.conversation.photos.contains(photo)
>     default:
>       return false
>     }
>   }
> }
>
> But this is not possible if there may be subclasses outside of the module,
> and it either forces all of the implementations into a single method body
> or requires adding dummy methods to each class.
>
> <https://github.com/jrose-apple/swift-evolution/tree/overridable-members-in-extensions#proposed-solution>Proposed
> solution
>
> This proposal lifts the restriction on non- at objc extension methods (and
> properties, and subscripts) by requiring an alternate dispatch mechanism
> that can be arbitrarily extended. To preserve safety and correctness, a
> new, narrower restriction will be put in place:
>
> *If an extension in module B is extending a class in module A, it may only
> override members added in module B.*
>
> Any other rule can result in two modules trying to add an override for the
> same method on the same class.
>
> Note: This rule applies to @objc members as well as non- at objc members.
>
> There is no restriction on extensions adding new *overridable* members.
> These members can be overridden by any extension in the same module (by the
> above rule) and by a subclass in any module, whether in the class
> declaration itself or in an extension in the same module.
>
> <https://github.com/jrose-apple/swift-evolution/tree/overridable-members-in-extensions#detailed-design>Detailed
> design
>
> Besides safety, the other reason we didn't add this feature is because the
> Swift method dispatch mechanism uses a single virtual dispatch table for a
> class, which cannot be arbitrarily extended after the fact. The
> implementation would require an alternate dispatch mechanism that *can* be
> arbitrarily extended.
>
> On Apple platforms this is implemented by the Objective-C method table; we
> would provide a simplified implementation of the same on Linux. For a
> selector we would use the mangled name of the original overridden method.
> These methods would still use Swift calling conventions; they're just being
> stored in the same lookup table as Objective-C methods.
>
> <https://github.com/jrose-apple/swift-evolution/tree/overridable-members-in-extensions#library-evolution>Library
> Evolution
>
> As with any other method
> <https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#classes>,
> it is legal to "move" an extension method up to an extension on the base
> class, as long as the original declaration is not removed entirely. The new
> entry point will forward over to the original entry point in order to
> preserve binary compatibility.
>
> <https://github.com/jrose-apple/swift-evolution/tree/overridable-members-in-extensions#impact-on-existing-code>Impact
> on existing code
>
> No existing semantics will be affected. Dispatch for methods in extensions
> may get slower, since it's no longer using a direct call.
>
> <https://github.com/jrose-apple/swift-evolution/tree/overridable-members-in-extensions#alternatives-considered>Alternatives
> considered
> <https://github.com/jrose-apple/swift-evolution/tree/overridable-members-in-extensions#all-extension-methods-are-final>All
> extension methods are final
>
> This is sound, and doesn't rule out the "closed class hierarchy" partial
> workaround described above. However, it does prevent some reasonable
> patterns that were possible in Objective-C, and it is something we've seen
> developers try to do (with or without @objc).
>
> <https://github.com/jrose-apple/swift-evolution/tree/overridable-members-in-extensions#objc-extension-methods-are-overridable-non-objc-methods-are-not>
> @objc extension methods are overridable, non- at objc methods are not
>
> This is a practical answer, since it requires no further implementation
> work. We could require members in extensions to be explicitly annotated
> dynamic and final, respectively, so that the semantics are at least
> clear. However, it's not a very principled design choice: either
> overridable extension members are useful, or they aren't.
>
> <https://github.com/jrose-apple/swift-evolution/tree/overridable-members-in-extensions#future-extensions>Future
> extensions
>
> The restriction that an extension cannot override a method from another
> module is intended for safety purposes, preventing two modules from each
> adding their own override. It's possible to make this a link-time failure
> rather than a compile-time failure by emitting a dummy symbol representing
> the (class, member) pair. Because of this, it may be useful to have an "I
> know what I'm doing" annotation that promises that no one else will add the
> same member; if it does happen then the program will fail to link.
>
> (Indeed, we probably should do this anyway for @objc overrides, which run
> the risk of run-time collision because of Objective-C language semantics.)
> If we ever have an "SPI" feature that allows public API to be restricted
> to certain clients, it would be reasonable to consider relaxing the safety
> restrictions for those clients specifically on the grounds that the library
> author trusts them to know what they're doing.
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
>
> _______________________________________________
> 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/20160218/05ccb1c7/attachment.html>


More information about the swift-evolution mailing list