Proposal: Allow @available(*, unavailable) methods to block resolving to an inherited protocol method


(Lily Ballard) #1

Right now, providing a method marked with @available(*, unavailable) doesn't actually prevent anyone from calling that method if the type in question inherits the same method from any protocol. Instead the unavailable method is ignored. This is in contrast with overriding a method from your superclass, where the @available(*, unavailable) annotation works just fine (as long as you're invoking the method with the correct compile-time receiver type).

I believe this should be changed such that if the type that declares the @available(*, unavailable) method inherits from a protocol, it blocks the ability to resolve that method to the protocol version. On the other hand, if a class declares an unavailable method, and a subclass conforms to a protocol that provides the method (and the base class does not conform to the protocol), the protocol method should be accessible as it's not "blocked" by the parent class.

This logic should be the same for methods declared in the protocol as well as methods provided by extensions to the protocol (as long as the extension is visible to the type that declares the @available(*, unavailable) method).

The rationale for doing this is twofold:

1. If a particular implementation of a protocol can't actually support one of the extension methods, for example an infinite sequence can't possibly support an eager map() or reduce() (as it would result in an infinite loop), the concrete type could declare the method as @available(*, unavailable, message="this would loop infinitely, try the lazy version"). Any code that attempts to call the method on the concrete type would then provide a nice compile-time error instead of failing at runtime. Of course, any code that calls the method on a generic type <T: Proto> would not be aware of this, but there's not much we can do about that.

2. Protocols like LazySequenceType that provide alternate versions of inherited methods may want to mark the inherited method as unavailable. In Swift 2.1, the code `someCol.lazy.filter(pred).first` compiles just fine, but it's not actually lazy; because SequenceType does not have a property `first`, the call to `.filter(pred)` actually resolves to the eager version inherited from SequenceType. This is likely to be very surprising to users (who may not even be aware of their accidental O(N) code), and so it would make sense for LazySequenceType to mark the inherited eager versions of map/filter/etc as unavailable.

-Kevin Ballard