Ease restrictions on protocol nesting

The proposal has been refined since the previous discussions. In particular:

- No capturing is allowed between nested types (beyond what already exists for nested generics).
- Access control for nested types in protocols.
- Nested types in constrained protocol extensions mean the same as in unconstrained extensions.
- Nested protocols in generic types are not parameterised by the parent's generic parameters.
- Some observations as to how capturing might be implemented in the future, standard library benefits.

This is a chance for everybody to go over it once more before the actual review.

Github: https://github.com/apple/swift-evolution/pull/552
Raw:

···

--------
# Ease restrictions on protocol nesting

* Proposal: [SE-XXXX](xxxx-ease-protocol-nesting.md)
* Authors: [Karl Wagner](https://github.com/karlwa)
* Review Manager: TBD
* Status: **Awaiting review**

*During the review process, add the following fields as needed:*

* Decision Notes: [Rationale](https://lists.swift.org/pipermail/swift-evolution/), [Additional Commentary](https://lists.swift.org/pipermail/swift-evolution/)
* Bugs: [SR-NNNN](https://bugs.swift.org/browse/SR-NNNN), [SR-MMMM](https://bugs.swift.org/browse/SR-MMMM)
* Previous Revision: [1](https://github.com/apple/swift-evolution/blob/...commit-ID.../proposals/NNNN-filename.md)
* Previous Proposal: [SE-XXXX](XXXX-filename.md)

## Introduction

Protocols define a way to express a syntactic and semantic contract. This semantic nature means that protocols are often intended to used in the context of one specific type (such as a 'delegate' protocol). Similarly, protocols sometimes wish to define specific types to be used within the context of that protocol (usually an `enum`).

This proposal would allow protocols to be nested in other types (including other protocols), and for structural types to be nested inside of protocols -- subject to a few constraints.

Swift-evolution thread: [Discussion thread topic for that proposal](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161017/028112.html)

## Motivation

Nesting types inside other types allows us to scope their usage and provide a cleaner interface. Protocols are an important part of Swift, and many popular patterns (for example, the delegate pattern) define protocols which are intended to be used in the semantic context of other types. It would be nice to apply type-nesting here: `MyClass.Delegate` reads better than `MyClassDelegate`, and literally brings structure to large frameworks.

Similarly, we have examples of protocols in the standard library which define supporting types to be used in the context of that protocol - `FloatingPointClassification`, `FloatingPointSign`, and `FloatingPointRoundingRule` are enums which are used by various members of the `FloatingPoint` protocol. These types are part of the contract which the protocol defines, and so it would be nice if they could be nested within the protocol to reflect that (i.e. `FloatingPoint.Classification`, `FloatingPoint.Sign`, `FloatingPoint.RoundingRule`).

## Proposed solution

The first part is to allow protocols to be nested inside of structural types (for example, in the delegate pattern):

class AView {                    // A regular-old class
    protocol Delegate: class {   // A nested protocol
        func somethingHappened()
    }
    weak var delegate: Delegate?
}

class MyDelegate: AView.Delegate {
    func somethingHappened() { /* ... */ }
}

The second part is to allow nominal types to be nested inside of protocols (for example, `FloatingPoint.Sign`).

protocol FloatingPoint {  
    // 'Sign' is required for conformance, therefore good candidate for nesting.
    enum Sign {
        case plus
        case minus
    }
    var sign: Sign { get }
}

Similarly, protocols may be nested inside other protocols:

protocol Scrollable: class {     // A regular-old protocol.
    var currentPosition: Position { get }
    
    protocol Delegate: class {   // A nested protocol.
        func scrollableDidScroll(_: Scrollable, from: Position)
    }    
    weak var delegate: Delegate?
}

class MyScrollable: Scrollable {
    var currentPosition = Position.zero
    
    weak var delegate: Delegate?
}

extension MyController: Scrollable.Delegate {
    func scrollableDidScroll(_ scrollable: Scrollable, from: Position) { 
        let displacement = scrollable.currentPosition.x - from.x
        // ...
    }
}

**Namespacing:**

It is important to draw a distinction between a protocol's nested types and its associated types. Associated types are placeholders (similar to generic type parameters), to be defined individually by each type which conforms to the protocol (e.g. every `Collection` will have a unique type of `Element`). Nested types are standard nominal types, and they don't neccessarily have anything to do with the conforming type (e.g. they may have been added in a protocol extension). We would like to avoid importing _irrelevant_ types in to the namespaces of conformers, but at the same time provide a convenient interface for any _relevant_ nested types (e.g. `FloatingPoint.Sign` is clearly relevant to `Float`, and it would be nice to call it `Float.Sign`).

To achieve this, the compiler will generate hidden typealiases for all nested types which are used by the protocol's requirements. The `FloatingPoint` protocol requires that its members have a property of type `FloatingPoint.Sign`, so it is clearly a relevant type for anybody who conforms to `FloatingPoint`, and should be made convenient. The exception is nested protocols, which are not implicitly imported even if used by a requirement. For example:

protocol FloatingPoint {
   enum Sign {
       case plus
       case minus
   }
   var sign: Sign { get }   // 'Sign' is used by a requirement. exported via typealiases.
}

struct Float: FloatingPoint {
    // [implicit] typealias Sign = FloatingPoint.Sign
    var sign: Sign { /* ... */ } // Sugared type: Float.Sign (via typealias)
}

let _: FloatingPoint.Sign = (4.0 as Float).sign as Double.Sign // all good B)

Looking at the Scollable example again, we want to ensure programmers write `Scrollable.Delegate` when writing their conformances and do not want to allow `MyScrollable.Delegate` as an alternative. For that reason, the compiler will not synthesise typealiases for nested protocols:

protocol Scrollable: class {
    protocol Delegate: class {
        func scrollableDidScroll(_: Scrollable, from: Position)
    }    
    weak var delegate: Delegate? // nested protocol 'Delegate' used by requirement, but _not_ imported!.
}

class MyScrollable: Scrollable {
    // _no_ implicit typealias!
    weak var delegate: Delegate? // Unqualified lookup. Type is: Optional<Scrollable.Delegate>
}

extension MyController: Scrollable.Delegate { // <- We don't want to allow 'MyScrollable.Delegate' here!
    func scrollableDidScroll(_ scrollable: Scrollable, from: Position) { 
        // ...
    }
}

Other nested types (i.e. those not used by requirements) are also not implicitly imported. Consider the following example of a struct which is added to `RandomAccessCollection` by an extension; it has nothing to do with any particular Collection (because it is generic), so it is irrelevant to any particular conforming type:

extension RandomAccessCollection {
    /// A view of a collection which provides concurrent implementations of map, filter, forEach, etc..
    struct Concurrent<T: RandomAccessCollection> { /* ... */ }
    
    var concurrent: Concurrent<Self> { return Concurrent(self) }  // is not a requirement. No implicit typealiases for Concurrent<T>.
}

let _ = [1, 2, 3].concurrent   // type is: RandomAccessCollection.Concurrent<Array<Int>>, _not_ Array<Int>.Concurrent

It would be possible to work around this with a typealias. We might consider synthesising these too once we have the ability to have default parameters for generic types. This would give us a convenient way to namespace 'views' of protocol conformers, as the standard library already does with `LazySequence<S>` and `JoinedSequence<S, T>`:

extension RandomAccessCollection {
    // Default is bound to 'Parent' (`Self` of the parent context), which we implement by creating a typealias.
    struct Concurrent<T: RandomAccessCollection = Parent> { /* ... */ }

    // [implicit] typealias Concurrent = Concurrent<Self>
}

let _: Array<Int>.Concurrent = [1, 2, 3].concurrent // sugared type: Array<Int>.Concurrent
                                                    // canonical type: RandomAccessCollection.Concurrent<Array<Int>>

**Access Control:**

Currently, members of a protocol declaration may not have access-control modifiers. That should apply for nested type declarations, too. The nested type itself, however, may contain members with limited visibility (including a lack of visible initialisers). The exception is that class types may include `open` or `final` modifiers.

public protocol MyProtocol {    
    final class Context {                   // 'MyProtocol.Context' may not have any access control modifiers. Visibility: public.
        fileprivate let _parent: MyProtocol // 'MyProtocol.Context._parent', however, may have limited access.
        // No public initialisers. That's allowed.
    }
}

Nested types may also be declared inside of protocol extensions. Consistent with current language rules, nested type declarations inside of protocol extensions _may_ have access control:

extension FloatingPoint {
    internal enum SignOrZero {  // 'FloatingPoint.SignOrZero' may have limited access.
        case plus
        case minus
        case zero
    }
    internal var signOrZero: SignOrZero {
        if self == 0.0 { 
            return .zero
        }
        else switch self.sign {
           case .plus:  return .plus
           case .minus: return .minus
        }
    }
}

**Constrained extensions:**

Nested types may also be defined inside of constrained protocol extensions, although they share a single namespace with unconstrained extensions:

// View as a series of UTF8 characters
extension Collection where Element == UInt8 {
    struct UnicodeCharacterView: Collection { let _wrapped: AnyCollection<UInt8> } // Type: Collection.UnicodeCharacterView
}

// View as a series of UTF16 characters
extension Collection where Element == UInt16 {
    struct UnicodeCharacterView: Collection { let _wrapped: AnyCollection<UInt16> } // ERROR: Redefinition of type 'Collection.UnicodeCharacterView'. Workaround is to make unique, e.g. 'Collection.UTF16View'.
}

**Generic types:**

Protocols may be nested inside of generic types, but they are not parameterised by any generic parameters. In the example below, there is only one nested protocol: `MyCollectionView.SelectionDelegate`, not `MyCollectionView<Book>.SelectionDelegate` or `MyCollectionView<Movie>.SelectionDelegate`:

class MyCollectionView<MediaItem> : UICollectionView {

    protocol SelectionDelegate: class {
        func itemWasSelected(at: Index)
    }
    weak var selectionDelegate: SelectionDelegate?
}

class MyDelegate: MyCollectionView.SelectionDelegate { // Use of unparameterised 'MyCollectionView' to refer to nested protocol.
    func itemWasSelected(at: Index) { /* ... */ }
}

MyCollectionView<Book>.selectionDelegate  = MyDelegate()
MyCollectionView<Song>.selectionDelegate  = MyDelegate()
MyCollectionView<Movie>.selectionDelegate = MyDelegate()

### Limitations

This proposal leaves one major limitation on protocol nesting: that nested types may not capture any types from (or through) a parent protocol. There is a 2x2 matrix of cases to consider here: when a nested protocol/nominal type captures a type parameter from a parent protocol/nominal types. The TLDR version is:

Capture from parent (V)\ by nested (H) | Protocol | Nominal Type |
------------- | ------------- |---|
Protocol | No | No |
Nominal Type | No | Yes! but not through a protocol. |

Essentially this is due to compiler limitations around existentials. If/when the compiler is capable of more comprehensive existentials, we can revisit capturing across nested generic protocols/types. There are enough useful cases which do not depend on this ability (including in the standard library) that it's worth implementing what we can today, though.

## Detailed Design

Given that nesting infers some context to the inner type, and that there is some friction between protocols with associated types ("generic protocols") and generic types, this section seeks to clarify when capturing is/is not allowed. Although it references compiler limitations surrounding existentials, any such changes are _not a part of this proposal_.

- Protocols may not capture associated types

    ```swift
    protocol Stream {
        associatedtype Content
        protocol Receiver {
            func receive(content: Content) // ERROR: Cannot capture associated type 'Content' from 'Stream'
        }
        var receiver: Receiver { get set }
    }
    ```
    
    Fundamentally, this is a compiler limitation. Ideally, we would like to represent the capture roughly so:
    
    ```swift
    protocol Stream {
        associatedtype Content
        protocol Receiver {
            // [implicit] associatedtype Content
            func receive(content: Content)
        }
        var receiver: Any<Receiver where .Content == Content> { get set } // Not possible today.
    }
    ```
    
    Should this limitation be lifted, we can revisit capturing of associated types.
    
- Protocols may not capture generic type parameters:

    Even if we wanted to do this with an implicit associated type, as mentioned above we couldn't represent the constrained protocol existential in the parent. Since it is a stated decision to avoid parameterised protocols, any capturing which may be allowed in the future would be expressed via existentials on an unparameterised protocol anyway, possibly looking something like this:

    ```swift
    class MyCollectionView<MediaItem> : UICollectionView {

        protocol Source {
            // [implicit] associatedtype MediaItem
            func item(at: Int) -> MediaItem
            var numberOfItems: Int { get }
        }
        var source: Any<MyCollectionView.Source where .MediaItem == MediaItem> // Not possible today.
    }

    class BookSource: MyCollectionView.Source {
        typealias MediaItem = Book

        func item(at: Int) -> Book { /* ... */ }
        var numberOfItems: Int { /* ... */ }
    }
    
    class DummySource<MediaItem>: MyCollectionView.Source where MediaItem: DummyConstructable {
        // associatedtype 'MediaItem' bound to generic parameter.
        
        func item(at: Int) -> MediaItem { /* ... */ }
        var numberOfItems: Int { /* ... */ }
    }

    MyCollectionView<Book>().source = BookSource()
    MyCollectionView<Book>().source = DummySource<Book>()
    MyCollectionView<Song>().source = DummySource() // type is: DummySource<Song>
    MyCollectionView<Movie>().source = DummySource() // type is: DummySource<Movie>
    ```

- Nominal types *may* capture generic type parameters, but not through a protocol

    Nominal types can already have nested types which capture parameters from their parents, and this proposal does not change that. However if we consider the possible capture hierarchies when protocols are involved, one situation is noteworthy:

    ```swift
    struct Top<X> {
        protocol Middle {
            enum Bottom {
                case howdy(X) // ERROR: Cannot capture 'X' from Top<X>
            }
            
            var bottomInstance : Bottom { get } // Would require capturing 'X'
        }
    }
    ```

    It isn't possible to refer to `Bottom` (or any types nested below it) from `Middle`, due to the above limitation on protocols capturing generic type parameters. Therefore the nesting is meaningless and should not be allowed.
    
- Nominal types may not capture associated types

    Consider the `RandomAccessCollection.Concurrent` example from before, if it were allowed to directly capture associated types from its enclosing protocol (rather than a magic potion of generics, defaults and typealiases):

     ```swift
    // Note: Pretend there is something called 'Parent' which is a captured 'Self' of the parent protocol.
    protocol RandomAccessCollection {

        struct Concurrent: RandomAccessCollection {
            typealias Element = Parent.Element
            typealias Index = Parent.Index
            init(with: Parent) { /* ... */ }
        }
        var concurrent: Concurrent { return Concurrent(self) }
    }
    ```

    By capturing associated types, the type `RandomAccessCollection.Concurrent` would also become existential (something like `RAC.Concurrent where Parent == Array<Int>`). We could theoretically map the capture of 'Parent' in to a generic parameter behind-the-scenes, but this kind of capturing would only work for would-be captures from the immediate parent before we start having the familiar problem of protocols capturing associated types. It would be better to tackle capturing between nested protocol types seperatetely at a later date.
    
    ```swift
    protocol Top {
        associatedtype AssocTop
        
        protocol Middle {
            associatedtype AssocMiddle
            
            enum Result { // [implicit] Result<Parent: Middle, Parent_Parent: Top>
                case one(AssocMiddle) // [implicit] Parent.AssocMidle
                case two(AssocTop) // [implicit] Parent_Parent.AssocTop
            }
            var result: Result { get } // [implicit] Result<Self, ???> - would need to capture 'Self' from Parent
        }
    }
    ```

That's a long explanation of why it's best to just bar any kind of capturing between protocols and structural types for now. We can maybe address this limitation at a later date, as part of broader support for existentials and according to demand.

## Source compatibility

Standard library changes making use of this feature will be part of another proposal.

Outside of the standard library, it is likely that the Clang importer could make use of this feature, as the delegate pattern is very common in Apple's platform SDKs. Changes such as `UITableViewDelegate` -> `UITableView.Delegate` can be migrated with a deprecated typealias:

@deprecated("Use UITableView.Delegate instead")
typealias UITableViewDelegate = UITableView.Delegate

## Effect on ABI stability

This proposal is only about the language feature, but it is likely to result in standard library and platform SDK changes.

## Effect on API resilience

Since all capturing is disallowed, this type of nesting would only change the name (in source and symbolic) of the relevant types.

## Alternatives considered

- The alternative to nesting is to namespace your types manually with a prefix, similar to what the standard library, Apple SDK overlays, and existing Swift programs already do. However, nested types and cleaner namespaces are one of the underrated benefits that developers - espcially coming from Objective-C - have always been excited about. We like clean and elegant APIs. From time to time somebody pops up on the mailing list to ask why we don't have this feature yet, and the changes proposed here usually are met with broad support.

- Nesting a structural type (with function bodies) inside of a protocol declaration is expected to be a little controversial. An alternative would be to require nested types to be defined inside of protocol extensions. This proposal leaves that as an option (indeed, that is the only way to provide access control for nested types), but does not require it. We don't encourage protocol declarations containing large concrete type bodies because those details are usually irrelevant to those looking to conform to the protocol, but for trivial cases it may be acceptable; that judgement is left to the programmer.

1 Like

- Nested protocols in generic types are not parameterised by the parent's generic parameters.

So if I write GenericType<Int>.SomeProto and GenericType<String>.SomeProto, is it the same protocol? What about GenericType.SomeProto, is that allowed?

Slava

···

On Feb 6, 2017, at 9:00 PM, Karl Wagner via swift-evolution <swift-evolution@swift.org> wrote:

- Some observations as to how capturing might be implemented in the future, standard library benefits.

This is a chance for everybody to go over it once more before the actual review.

Github: https://github.com/apple/swift-evolution/pull/552
Raw:
--------
# Ease restrictions on protocol nesting

* Proposal: [SE-XXXX](xxxx-ease-protocol-nesting.md)
* Authors: [Karl Wagner](https://github.com/karlwa)
* Review Manager: TBD
* Status: **Awaiting review**

*During the review process, add the following fields as needed:*

* Decision Notes: [Rationale](https://lists.swift.org/pipermail/swift-evolution/), [Additional Commentary](https://lists.swift.org/pipermail/swift-evolution/)
* Bugs: [SR-NNNN](https://bugs.swift.org/browse/SR-NNNN), [SR-MMMM](https://bugs.swift.org/browse/SR-MMMM)
* Previous Revision: [1](https://github.com/apple/swift-evolution/blob/...commit-ID.../proposals/NNNN-filename.md)
* Previous Proposal: [SE-XXXX](XXXX-filename.md)

## Introduction

Protocols define a way to express a syntactic and semantic contract. This semantic nature means that protocols are often intended to used in the context of one specific type (such as a 'delegate' protocol). Similarly, protocols sometimes wish to define specific types to be used within the context of that protocol (usually an `enum`).

This proposal would allow protocols to be nested in other types (including other protocols), and for structural types to be nested inside of protocols -- subject to a few constraints.

Swift-evolution thread: [Discussion thread topic for that proposal](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161017/028112.html)

## Motivation

Nesting types inside other types allows us to scope their usage and provide a cleaner interface. Protocols are an important part of Swift, and many popular patterns (for example, the delegate pattern) define protocols which are intended to be used in the semantic context of other types. It would be nice to apply type-nesting here: `MyClass.Delegate` reads better than `MyClassDelegate`, and literally brings structure to large frameworks.

Similarly, we have examples of protocols in the standard library which define supporting types to be used in the context of that protocol - `FloatingPointClassification`, `FloatingPointSign`, and `FloatingPointRoundingRule` are enums which are used by various members of the `FloatingPoint` protocol. These types are part of the contract which the protocol defines, and so it would be nice if they could be nested within the protocol to reflect that (i.e. `FloatingPoint.Classification`, `FloatingPoint.Sign`, `FloatingPoint.RoundingRule`).

## Proposed solution

The first part is to allow protocols to be nested inside of structural types (for example, in the delegate pattern):

class AView {                    // A regular-old class
    protocol Delegate: class {   // A nested protocol
        func somethingHappened()
    }
    weak var delegate: Delegate?
}

class MyDelegate: AView.Delegate {
    func somethingHappened() { /* ... */ }
}

The second part is to allow nominal types to be nested inside of protocols (for example, `FloatingPoint.Sign`).

protocol FloatingPoint {  
    // 'Sign' is required for conformance, therefore good candidate for nesting.
    enum Sign {
        case plus
        case minus
    }
    var sign: Sign { get }
}

Similarly, protocols may be nested inside other protocols:

protocol Scrollable: class {     // A regular-old protocol.
    var currentPosition: Position { get }
   
    protocol Delegate: class {   // A nested protocol.
        func scrollableDidScroll(_: Scrollable, from: Position)
    }    
    weak var delegate: Delegate?
}

class MyScrollable: Scrollable {
    var currentPosition = Position.zero
   
    weak var delegate: Delegate?
}

extension MyController: Scrollable.Delegate {
    func scrollableDidScroll(_ scrollable: Scrollable, from: Position) { 
        let displacement = scrollable.currentPosition.x - from.x
        // ...
    }
}

**Namespacing:**

It is important to draw a distinction between a protocol's nested types and its associated types. Associated types are placeholders (similar to generic type parameters), to be defined individually by each type which conforms to the protocol (e.g. every `Collection` will have a unique type of `Element`). Nested types are standard nominal types, and they don't neccessarily have anything to do with the conforming type (e.g. they may have been added in a protocol extension). We would like to avoid importing _irrelevant_ types in to the namespaces of conformers, but at the same time provide a convenient interface for any _relevant_ nested types (e.g. `FloatingPoint.Sign` is clearly relevant to `Float`, and it would be nice to call it `Float.Sign`).

To achieve this, the compiler will generate hidden typealiases for all nested types which are used by the protocol's requirements. The `FloatingPoint` protocol requires that its members have a property of type `FloatingPoint.Sign`, so it is clearly a relevant type for anybody who conforms to `FloatingPoint`, and should be made convenient. The exception is nested protocols, which are not implicitly imported even if used by a requirement. For example:

protocol FloatingPoint {
   enum Sign {
       case plus
       case minus
   }
   var sign: Sign { get }   // 'Sign' is used by a requirement. exported via typealiases.
}

struct Float: FloatingPoint {
    // [implicit] typealias Sign = FloatingPoint.Sign
    var sign: Sign { /* ... */ } // Sugared type: Float.Sign (via typealias)
}

let _: FloatingPoint.Sign = (4.0 as Float).sign as Double.Sign // all good B)

Looking at the Scollable example again, we want to ensure programmers write `Scrollable.Delegate` when writing their conformances and do not want to allow `MyScrollable.Delegate` as an alternative. For that reason, the compiler will not synthesise typealiases for nested protocols:

protocol Scrollable: class {
    protocol Delegate: class {
        func scrollableDidScroll(_: Scrollable, from: Position)
    }    
    weak var delegate: Delegate? // nested protocol 'Delegate' used by requirement, but _not_ imported!.
}

class MyScrollable: Scrollable {
    // _no_ implicit typealias!
    weak var delegate: Delegate? // Unqualified lookup. Type is: Optional<Scrollable.Delegate>
}

extension MyController: Scrollable.Delegate { // <- We don't want to allow 'MyScrollable.Delegate' here!
    func scrollableDidScroll(_ scrollable: Scrollable, from: Position) { 
        // ...
    }
}

Other nested types (i.e. those not used by requirements) are also not implicitly imported. Consider the following example of a struct which is added to `RandomAccessCollection` by an extension; it has nothing to do with any particular Collection (because it is generic), so it is irrelevant to any particular conforming type:

extension RandomAccessCollection {
    /// A view of a collection which provides concurrent implementations of map, filter, forEach, etc..
    struct Concurrent<T: RandomAccessCollection> { /* ... */ }
   
    var concurrent: Concurrent<Self> { return Concurrent(self) }  // is not a requirement. No implicit typealiases for Concurrent<T>.
}

let _ = [1, 2, 3].concurrent   // type is: RandomAccessCollection.Concurrent<Array<Int>>, _not_ Array<Int>.Concurrent

It would be possible to work around this with a typealias. We might consider synthesising these too once we have the ability to have default parameters for generic types. This would give us a convenient way to namespace 'views' of protocol conformers, as the standard library already does with `LazySequence<S>` and `JoinedSequence<S, T>`:

extension RandomAccessCollection {
    // Default is bound to 'Parent' (`Self` of the parent context), which we implement by creating a typealias.
    struct Concurrent<T: RandomAccessCollection = Parent> { /* ... */ }

    // [implicit] typealias Concurrent = Concurrent<Self>
}

let _: Array<Int>.Concurrent = [1, 2, 3].concurrent // sugared type: Array<Int>.Concurrent
                                                    // canonical type: RandomAccessCollection.Concurrent<Array<Int>>

**Access Control:**

Currently, members of a protocol declaration may not have access-control modifiers. That should apply for nested type declarations, too. The nested type itself, however, may contain members with limited visibility (including a lack of visible initialisers). The exception is that class types may include `open` or `final` modifiers.

public protocol MyProtocol {    
    final class Context {                   // 'MyProtocol.Context' may not have any access control modifiers. Visibility: public.
        fileprivate let _parent: MyProtocol // 'MyProtocol.Context._parent', however, may have limited access.
        // No public initialisers. That's allowed.
    }
}

Nested types may also be declared inside of protocol extensions. Consistent with current language rules, nested type declarations inside of protocol extensions _may_ have access control:

extension FloatingPoint {
    internal enum SignOrZero {  // 'FloatingPoint.SignOrZero' may have limited access.
        case plus
        case minus
        case zero
    }
    internal var signOrZero: SignOrZero {
        if self == 0.0 { 
            return .zero
        }
        else switch self.sign {
           case .plus:  return .plus
           case .minus: return .minus
        }
    }
}

**Constrained extensions:**

Nested types may also be defined inside of constrained protocol extensions, although they share a single namespace with unconstrained extensions:

// View as a series of UTF8 characters
extension Collection where Element == UInt8 {
    struct UnicodeCharacterView: Collection { let _wrapped: AnyCollection<UInt8> } // Type: Collection.UnicodeCharacterView
}

// View as a series of UTF16 characters
extension Collection where Element == UInt16 {
    struct UnicodeCharacterView: Collection { let _wrapped: AnyCollection<UInt16> } // ERROR: Redefinition of type 'Collection.UnicodeCharacterView'. Workaround is to make unique, e.g. 'Collection.UTF16View'.
}

**Generic types:**

Protocols may be nested inside of generic types, but they are not parameterised by any generic parameters. In the example below, there is only one nested protocol: `MyCollectionView.SelectionDelegate`, not `MyCollectionView<Book>.SelectionDelegate` or `MyCollectionView<Movie>.SelectionDelegate`:

class MyCollectionView<MediaItem> : UICollectionView {

    protocol SelectionDelegate: class {
        func itemWasSelected(at: Index)
    }
    weak var selectionDelegate: SelectionDelegate?
}

class MyDelegate: MyCollectionView.SelectionDelegate { // Use of unparameterised 'MyCollectionView' to refer to nested protocol.
    func itemWasSelected(at: Index) { /* ... */ }
}

MyCollectionView<Book>.selectionDelegate  = MyDelegate()
MyCollectionView<Song>.selectionDelegate  = MyDelegate()
MyCollectionView<Movie>.selectionDelegate = MyDelegate()

### Limitations

This proposal leaves one major limitation on protocol nesting: that nested types may not capture any types from (or through) a parent protocol. There is a 2x2 matrix of cases to consider here: when a nested protocol/nominal type captures a type parameter from a parent protocol/nominal types. The TLDR version is:

> Capture from parent (V)\ by nested (H) | Protocol | Nominal Type |
> ------------- | ------------- |---|
> Protocol | No | No |
> Nominal Type | No | Yes! but not through a protocol. |

Essentially this is due to compiler limitations around existentials. If/when the compiler is capable of more comprehensive existentials, we can revisit capturing across nested generic protocols/types. There are enough useful cases which do not depend on this ability (including in the standard library) that it's worth implementing what we can today, though.

## Detailed Design

Given that nesting infers some context to the inner type, and that there is some friction between protocols with associated types ("generic protocols") and generic types, this section seeks to clarify when capturing is/is not allowed. Although it references compiler limitations surrounding existentials, any such changes are _not a part of this proposal_.

- Protocols may not capture associated types

    ```swift
    protocol Stream {
        associatedtype Content
        protocol Receiver {
            func receive(content: Content) // ERROR: Cannot capture associated type 'Content' from 'Stream'
        }
        var receiver: Receiver { get set }
    }
    ```
    
    Fundamentally, this is a compiler limitation. Ideally, we would like to represent the capture roughly so:
    
    ```swift
    protocol Stream {
        associatedtype Content
        protocol Receiver {
            // [implicit] associatedtype Content
            func receive(content: Content)
        }
        var receiver: Any<Receiver where .Content == Content> { get set } // Not possible today.
    }
    ```
    
    Should this limitation be lifted, we can revisit capturing of associated types.
    
- Protocols may not capture generic type parameters:

    Even if we wanted to do this with an implicit associated type, as mentioned above we couldn't represent the constrained protocol existential in the parent. Since it is a stated decision to avoid parameterised protocols, any capturing which may be allowed in the future would be expressed via existentials on an unparameterised protocol anyway, possibly looking something like this:

    ```swift
    class MyCollectionView<MediaItem> : UICollectionView {

        protocol Source {
            // [implicit] associatedtype MediaItem
            func item(at: Int) -> MediaItem
            var numberOfItems: Int { get }
        }
        var source: Any<MyCollectionView.Source where .MediaItem == MediaItem> // Not possible today.
    }

    class BookSource: MyCollectionView.Source {
        typealias MediaItem = Book

        func item(at: Int) -> Book { /* ... */ }
        var numberOfItems: Int { /* ... */ }
    }
    
    class DummySource<MediaItem>: MyCollectionView.Source where MediaItem: DummyConstructable {
        // associatedtype 'MediaItem' bound to generic parameter.
        
        func item(at: Int) -> MediaItem { /* ... */ }
        var numberOfItems: Int { /* ... */ }
    }

    MyCollectionView<Book>().source = BookSource()
    MyCollectionView<Book>().source = DummySource<Book>()
    MyCollectionView<Song>().source = DummySource() // type is: DummySource<Song>
    MyCollectionView<Movie>().source = DummySource() // type is: DummySource<Movie>
    ```

- Nominal types *may* capture generic type parameters, but not through a protocol

    Nominal types can already have nested types which capture parameters from their parents, and this proposal does not change that. However if we consider the possible capture hierarchies when protocols are involved, one situation is noteworthy:

    ```swift
    struct Top<X> {
        protocol Middle {
            enum Bottom {
                case howdy(X) // ERROR: Cannot capture 'X' from Top<X>
            }
            
            var bottomInstance : Bottom { get } // Would require capturing 'X'
        }
    }
    ```

    It isn't possible to refer to `Bottom` (or any types nested below it) from `Middle`, due to the above limitation on protocols capturing generic type parameters. Therefore the nesting is meaningless and should not be allowed.
    
- Nominal types may not capture associated types

    Consider the `RandomAccessCollection.Concurrent` example from before, if it were allowed to directly capture associated types from its enclosing protocol (rather than a magic potion of generics, defaults and typealiases):

     ```swift
    // Note: Pretend there is something called 'Parent' which is a captured 'Self' of the parent protocol.
    protocol RandomAccessCollection {

        struct Concurrent: RandomAccessCollection {
            typealias Element = Parent.Element
            typealias Index = Parent.Index
            init(with: Parent) { /* ... */ }
        }
        var concurrent: Concurrent { return Concurrent(self) }
    }
    ```

    By capturing associated types, the type `RandomAccessCollection.Concurrent` would also become existential (something like `RAC.Concurrent where Parent == Array<Int>`). We could theoretically map the capture of 'Parent' in to a generic parameter behind-the-scenes, but this kind of capturing would only work for would-be captures from the immediate parent before we start having the familiar problem of protocols capturing associated types. It would be better to tackle capturing between nested protocol types seperatetely at a later date.
    
    ```swift
    protocol Top {
        associatedtype AssocTop
        
        protocol Middle {
            associatedtype AssocMiddle
            
            enum Result { // [implicit] Result<Parent: Middle, Parent_Parent: Top>
                case one(AssocMiddle) // [implicit] Parent.AssocMidle
                case two(AssocTop) // [implicit] Parent_Parent.AssocTop
            }
            var result: Result { get } // [implicit] Result<Self, ???> - would need to capture 'Self' from Parent
        }
    }
    ```

That's a long explanation of why it's best to just bar any kind of capturing between protocols and structural types for now. We can maybe address this limitation at a later date, as part of broader support for existentials and according to demand.

## Source compatibility

Standard library changes making use of this feature will be part of another proposal.

Outside of the standard library, it is likely that the Clang importer could make use of this feature, as the delegate pattern is very common in Apple's platform SDKs. Changes such as `UITableViewDelegate` -> `UITableView.Delegate` can be migrated with a deprecated typealias:

@deprecated("Use UITableView.Delegate instead")
typealias UITableViewDelegate = UITableView.Delegate

## Effect on ABI stability

This proposal is only about the language feature, but it is likely to result in standard library and platform SDK changes.

## Effect on API resilience

Since all capturing is disallowed, this type of nesting would only change the name (in source and symbolic) of the relevant types.

## Alternatives considered

- The alternative to nesting is to namespace your types manually with a prefix, similar to what the standard library, Apple SDK overlays, and existing Swift programs already do. However, nested types and cleaner namespaces are one of the underrated benefits that developers - espcially coming from Objective-C - have always been excited about. We like clean and elegant APIs. From time to time somebody pops up on the mailing list to ask why we don't have this feature yet, and the changes proposed here usually are met with broad support.

- Nesting a structural type (with function bodies) inside of a protocol declaration is expected to be a little controversial. An alternative would be to require nested types to be defined inside of protocol extensions. This proposal leaves that as an option (indeed, that is the only way to provide access control for nested types), but does not require it. We don't encourage protocol declarations containing large concrete type bodies because those details are usually irrelevant to those looking to conform to the protocol, but for trivial cases it may be acceptable; that judgement is left to the programmer.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

GenericType.SomeProto (without parameters) is the only spelling that is allowed. There is no GenericType<Int>.SomeProto.

That way we avoid every bound-generic type creating a new protocol.
I think it works really nicely when you consider what it would like like with existential-based capturing. Notice that there is only one ‚ÄėMyCollectionView.Source‚Äô, and compatibility is determined based on existential constraints.

- Karl

class MyCollectionView<MediaItem> : UICollectionView {

    protocol Source {
        // [implicit] associatedtype MediaItem
        func item(at: Int) -> MediaItem
        var numberOfItems: Int { get }
    }
    var source: Any<MyCollectionView.Source where .MediaItem == MediaItem> // Not possible today.
}

class BookSource: MyCollectionView.Source {
    typealias MediaItem = Book

    func item(at: Int) -> Book { /* ... */ }
    var numberOfItems: Int { /* ... */ }
}

class DummySource<MediaItem>: MyCollectionView.Source where MediaItem: DummyConstructable {
    // associatedtype 'MediaItem' bound to generic parameter.

    func item(at: Int) -> MediaItem { /* ... */ }
    var numberOfItems: Int { /* ... */ }
}

MyCollectionView<Book>().source = BookSource()
MyCollectionView<Book>().source = DummySource<Book>()
MyCollectionView<Song>().source = DummySource() // type is: DummySource<Song>
MyCollectionView<Movie>().source = DummySource() // type is: DummySource<Movie>

···

On 7 Feb 2017, at 06:05, Slava Pestov <spestov@apple.com> wrote:

On Feb 6, 2017, at 9:00 PM, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
- Nested protocols in generic types are not parameterised by the parent's generic parameters.

So if I write GenericType<Int>.SomeProto and GenericType<String>.SomeProto, is it the same protocol? What about GenericType.SomeProto, is that allowed?

Slava

- Nested protocols in generic types are not parameterised by the parent's generic parameters.

So if I write GenericType<Int>.SomeProto and GenericType<String>.SomeProto, is it the same protocol? What about GenericType.SomeProto, is that allowed?

Slava

GenericType.SomeProto (without parameters) is the only spelling that is allowed. There is no GenericType<Int>.SomeProto.

That way we avoid every bound-generic type creating a new protocol.
I think it works really nicely when you consider what it would like like with existential-based capturing. Notice that there is only one ‚ÄėMyCollectionView.Source‚Äô, and compatibility is determined based on existential constraints.

- Karl

class MyCollectionView<MediaItem> : UICollectionView {

    protocol Source {
        // [implicit] associatedtype MediaItem

I’m worried this is going to be tricky to implement; we definitely won’t get this with the initial implementation of nested protocol types.

        func item(at: Int) -> MediaItem
        var numberOfItems: Int { get }
    }
    var source: Any<MyCollectionView.Source where .MediaItem == MediaItem> // Not possible today.

I think for this use-case, it makes sense to allow the protocol itself to be parametrized.

Slava

···

On Feb 6, 2017, at 9:12 PM, Karl Wagner <razielim@gmail.com> wrote:

On 7 Feb 2017, at 06:05, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

On Feb 6, 2017, at 9:00 PM, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

}

class BookSource: MyCollectionView.Source {
    typealias MediaItem = Book

    func item(at: Int) -> Book { /* ... */ }
    var numberOfItems: Int { /* ... */ }
}

class DummySource<MediaItem>: MyCollectionView.Source where MediaItem: DummyConstructable {
    // associatedtype 'MediaItem' bound to generic parameter.

    func item(at: Int) -> MediaItem { /* ... */ }
    var numberOfItems: Int { /* ... */ }
}

MyCollectionView<Book>().source = BookSource()
MyCollectionView<Book>().source = DummySource<Book>()
MyCollectionView<Song>().source = DummySource() // type is: DummySource<Song>
MyCollectionView<Movie>().source = DummySource() // type is: DummySource<Movie>

- Nested protocols in generic types are not parameterised by the parent's generic parameters.

So if I write GenericType<Int>.SomeProto and GenericType<String>.SomeProto, is it the same protocol? What about GenericType.SomeProto, is that allowed?

Slava

GenericType.SomeProto (without parameters) is the only spelling that is allowed. There is no GenericType<Int>.SomeProto.

That way we avoid every bound-generic type creating a new protocol.

I have only had a chance to scan the proposal so I will probably have more comments after I take a closer look, but I want to respond to this topic right away.

I understand the reason why you want it to work this way, but think it will be a significant source of confusion. Protocols will behave differently than any other entity nested in a generic type. This means the protocols nested in generic types will likely be counterintuitive for most people.

I know this was the case for me while I was scanning the proposal. I had to think carefully to understand how you want this to work and why you want this behavior. That is despite it being a relatively direct consequence of the Swift’s design for protocols, which uses associated types and intentionally does not allow generic protocols.

It may be that there isn’t a good way to avoid that and we need to try to address it through documentation and education. But I think this should receive careful consideration before we commit to this direction.

Overall the proposal looks really great! I’m really looking forward to this feature.

···

On Feb 6, 2017, at 11:12 PM, Karl Wagner via swift-evolution <swift-evolution@swift.org> wrote:

On 7 Feb 2017, at 06:05, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

On Feb 6, 2017, at 9:00 PM, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I think it works really nicely when you consider what it would like like with existential-based capturing. Notice that there is only one ‚ÄėMyCollectionView.Source‚Äô, and compatibility is determined based on existential constraints.

- Karl

class MyCollectionView<MediaItem> : UICollectionView {

    protocol Source {
        // [implicit] associatedtype MediaItem
        func item(at: Int) -> MediaItem
        var numberOfItems: Int { get }
    }
    var source: Any<MyCollectionView.Source where .MediaItem == MediaItem> // Not possible today.
}

class BookSource: MyCollectionView.Source {
    typealias MediaItem = Book

    func item(at: Int) -> Book { /* ... */ }
    var numberOfItems: Int { /* ... */ }
}

class DummySource<MediaItem>: MyCollectionView.Source where MediaItem: DummyConstructable {
    // associatedtype 'MediaItem' bound to generic parameter.

    func item(at: Int) -> MediaItem { /* ... */ }
    var numberOfItems: Int { /* ... */ }
}

MyCollectionView<Book>().source = BookSource()
MyCollectionView<Book>().source = DummySource<Book>()
MyCollectionView<Song>().source = DummySource() // type is: DummySource<Song>
MyCollectionView<Movie>().source = DummySource() // type is: DummySource<Movie>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

1 Like

- Nested protocols in generic types are not parameterised by the parent's generic parameters.

So if I write GenericType<Int>.SomeProto and GenericType<String>.SomeProto, is it the same protocol? What about GenericType.SomeProto, is that allowed?

Slava

GenericType.SomeProto (without parameters) is the only spelling that is allowed. There is no GenericType<Int>.SomeProto.

That way we avoid every bound-generic type creating a new protocol.
I think it works really nicely when you consider what it would like like with existential-based capturing. Notice that there is only one ‚ÄėMyCollectionView.Source‚Äô, and compatibility is determined based on existential constraints.

- Karl

class MyCollectionView<MediaItem> : UICollectionView {

    protocol Source {
        // [implicit] associatedtype MediaItem

I’m worried this is going to be tricky to implement; we definitely won’t get this with the initial implementation of nested protocol types.

Do you mean that unparameterised protocols would be tricky to implement, or that capturing and implicit associated types would be? Because the latter isn’t part of the proposal - just sketching out how a capturing solution _might_ look.

        func item(at: Int) -> MediaItem
        var numberOfItems: Int { get }
    }
    var source: Any<MyCollectionView.Source where .MediaItem == MediaItem> // Not possible today.

I think for this use-case, it makes sense to allow the protocol itself to be parametrized.

I used to think so, but your comments on the PR and working through some examples made me prefer this way. I think it’s exactly what we mean when we say that associated types are preferred to generic protocols. It also means that adding or removing generic types from the parent does not affect the nested protocol in any way - it could still be mangled the same for ABI compatibility and spelled the same for source compatibility.

The syntax is not exactly ideal. I’m using the Any<...> syntax because I think it’s a bit easier to understand in the context of this discussion, but that’s already gone from the language, so really it would look closer to:

var source: Source where .MediaItem == MediaItem

Still not ideal. But then, we automatically infer generic type parameters inside a generic context. Maybe it would be reasonable to do the same in this context?

extension Array where Element: Comparable {

var reverseSorted: Array { //< inferred to be Array<T> by context
return self.sorted(by: >)
}
}

For capture-existentials, the analog may look something like:

var source: Source // [implicit] where .MediaItem == MediaItem, exhaustively binding all captured types. May be over-specific.

We would need a way to opt-out of that, though:

var source: Source where _ // Make the existential less specific with an explicit ‚Äėwhere‚Äô clause.

But none of that is part of the proposal. Again, just sketching what the situation might look like later if we took this approach. I’m curious to see what others feel about it.

- Karl

···

On 7 Feb 2017, at 06:18, Slava Pestov <spestov@apple.com> wrote:

On Feb 6, 2017, at 9:12 PM, Karl Wagner <razielim@gmail.com <mailto:razielim@gmail.com>> wrote:

On 7 Feb 2017, at 06:05, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

On Feb 6, 2017, at 9:00 PM, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Slava

}

class BookSource: MyCollectionView.Source {
    typealias MediaItem = Book

    func item(at: Int) -> Book { /* ... */ }
    var numberOfItems: Int { /* ... */ }
}

class DummySource<MediaItem>: MyCollectionView.Source where MediaItem: DummyConstructable {
    // associatedtype 'MediaItem' bound to generic parameter.

    func item(at: Int) -> MediaItem { /* ... */ }
    var numberOfItems: Int { /* ... */ }
}

MyCollectionView<Book>().source = BookSource()
MyCollectionView<Book>().source = DummySource<Book>()
MyCollectionView<Song>().source = DummySource() // type is: DummySource<Song>
MyCollectionView<Movie>().source = DummySource() // type is: DummySource<Movie>

I would love to see this, currently using a ‚Äúworkaround‚ÄĚ like this:

protocol FoobarDelegate { /* ... */ }
protocol FoobarDataSource { /* ... */ }

class Foobar {
    typealias Delegate = FoobarDelegate
    typealias DataSource = FoobarDataSource

    /* ... */
}

But this still clutters up the global namespace with FoobarDelegate and FoobarDataSource.

The core team discussed this PR for Swift 4 stage 2. It’s additive to the language and ABI (which would put it out of scope per the goals of stage 2 […]

@Karl are you up for resubmitting this for inclusion in Swift 5?

1 Like

Yes, definitely! We should be able to do something basic without capturing.

1 Like

Awesome!

It seems like Apple is actually using the pattern I described in their public documentation:

https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller.configuration

I’m not sure that’s quite the same, since both SFSafariViewController and SFSafariViewController.Configuration are classes.

Ah, of course, missed that :man_facepalming: :smile:

Up to today this feature is the highest on my Swift wishlist. Is there any progress in this at the moment?

1 Like

Any progress?

I don't think anyone worked on that since, so I guess there is no progress on that. Maybe we'll see it in Swift 6.

Terms of Service

Privacy Policy

Cookie Policy