You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 which must be used by _every_ type which conforms to the protocol. Taking `FloatingPoint.Sign` as an example, conformance to the `FloatingPoint` protocol means that every conforming type has a property called `sign` whose value may one of a few enum cases defined _as part of the protocol_.
87
+
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).
88
88
89
-
Since nested types are members of the protocol and not the conforming type, they are not implicitly imported in to the namespace of conforming types at all.
90
-
91
-
Take the standard library's `EmptyCollection<Element>` and `CollectionOfOne<Element>` types as an example of when a protocol may wish to define canned instances with a particular basic behaviour. With nesting, we would call these `Collection.Empty<Element>` and `Collection.One<Element>`; but suddenly every type which conforms to `Collection` has the pleasure of welcoming these irrelevant types in to its namespace:
89
+
Since nested types are members of the protocol and not the conforming type, they are not implicitly imported in to the namespace of conforming types. Consider the following example of a struct which is added to `RandomAccessCollection` by an extension; if the type of the result was `Array<T>.Concurrent` the user might expect that they are getting some kind of Array, with specialist Array methods, which is not the case.
92
90
93
91
```swift
94
-
let myArray =Array.Empty<Int>() // type: Collection.Empty<Int>, nothing to do with Array!
92
+
extensionRandomAccessCollection {
93
+
/// A view of a collection which provides concurrent implementations of
var concurrent: Concurrent<Self> { returnConcurrent(self) }
98
+
}
99
+
100
+
let _: = [1, 2, 3].concurrent// type is: RandomAccessCollection.Concurrent<Array<Int>>, not Array<Int>.Concurrent
95
101
```
96
102
97
-
For `Array.Empty<T>` to have its expected meaning, `Empty` would have to be static function requirement of `Collection` (which would make it `Array<T>.Empty` anyway -- but that's not part of this proposal). In short, `Array.Empty<T>` and `Collection.Empty<T>` have nothing whatsoever to do with each other. So we shouldn't import protocol types.
103
+
There are cases, however, when the protocol wishes its confomers to express a particular type. For example, `FloatingPoint` may want its confomers to have a `Sign` type. That is already expressible in the language today, as a typealias. As an exception, we allow protocols to define a typealias with the same name as a nested type, in order to have it inherited by conformers.
104
+
98
105
99
106
```swift
100
-
// See: FloatingPoint.Sign example above
107
+
protocolFloatingPoint {
108
+
enumSign {
109
+
caseplus
110
+
caseminus
111
+
}
112
+
typealiasSign// name-conflict allowed. Points to (enum) Sign.
113
+
114
+
var sign: Sign { get }
115
+
}
101
116
102
117
structFloat: FloatingPoint{
103
-
var sign: FloatingPoint.Sign { /* ... */ } //Qualified name!
118
+
var sign: Sign { /* ... */ } //Can use sugared name
104
119
}
120
+
```
105
121
106
-
let _: Double.Sign = (3.0asFloat).sign// ERROR: 'Double.Sign' does not exist - did you mean 'FloatingPoint.Sign'?
122
+
This is only a syntactic sugar. Typealiases are overridable, in which case the members revert to their unsugared name, `FloatingPoint.Sign`:
123
+
124
+
```swift
125
+
structMyFloat: FloatingPoint{
126
+
structSign { var isBillboard =true; var message ="Howdy!" } // This is potentially poor API design, but allowed.
127
+
128
+
var sign: FloatingPoint.Sign { /* ... */ }
129
+
}
107
130
```
108
131
132
+
109
133
**Access Control:**
110
134
111
135
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.
@@ -203,29 +227,29 @@ Given that there is some friction between protocols with associated types ("gene
203
227
204
228
- Protocols may not capture generic type parameters:
205
229
206
-
Firstly, even if we wanted to do this via some sort of implicit associated type, as mentioned above we couldn't represent the existential in the parent. Secondly, there is a concern about parameterised protocols.
230
+
Even if we wanted to do this with an implicit associated type, as mentioned above we couldn't represent the constrained protocolexistential in the parent. Secondly, there is a concern about parameterised protocols. So expect an error:
207
231
208
232
```swift
209
233
struct MyType<X> {
210
234
protocolMyProto {
211
-
var content: X { getset } // ERROR: Cannot capture 'X' from MyType
235
+
var content: X { getset } // ERROR: Cannot capture 'X' from MyType<X>
212
236
}
213
237
var protoInstance: MyProto
214
238
}
215
239
```
216
240
217
241
- Structural types *may* capture generic type parameters, but not through a protocol
218
242
219
-
Structural types can already have nested structural 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 isnotable:
243
+
Structural types can already have nested structural 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 isnoteworthy:
220
244
221
245
```swift
222
246
struct Top<X> {
223
247
protocolMiddle {
224
248
enumBottom {
225
-
casehowdy(X) // ERROR: Cannot capture 'X' from Top
249
+
casehowdy(X) // ERROR: Cannot capture 'X' from Top<X>
226
250
}
227
251
228
-
var bottomInstance : Bottom { get } //If it _was_ allowed, this reference would also capture 'X'
252
+
var bottomInstance : Bottom { get } //Would require capturing 'X'
229
253
}
230
254
}
231
255
```
@@ -234,41 +258,22 @@ Given that there is some friction between protocols with associated types ("gene
234
258
235
259
- Structual types may not capture associated types
236
260
237
-
This is quite a common pattern that we find inthe standard library, particularly for the dyanmic-'Self' associated type. For example: `Collection.Slice`, `LazyCollection`. This can lead to some really awkward-named types, like `RangeReplaceableBidirectionalSlice`...
261
+
Consider the `RandomAccessCollection.Concurrent` example from before, if it were allowed to capture associated types from its enclosing protocol:
238
262
239
263
```swift
240
264
// Note: Pretend there is something called 'Parent' which is a captured 'Self' of the parent protocol.
241
-
protocolCollection {
265
+
protocolRandomAccessCollection {
242
266
243
-
structSlice: Collection{
244
-
typealiasElement= Parent.Element// ERROR: Cannot capture 'Element' from Parent
245
-
typealiasIndex= Parent.Index// ERROR: Cannot capture 'Index' from Parent
var concurrent: Concurrent { returnConcurrent(self) }
250
273
}
251
274
```
252
275
253
-
By capturing an associated type, the type `Collection.Slice` would also become existential (something like `Collection.Slicewhere Parent ==Array`). We could theoretically map the capture of 'Parent' in to a generic parameter (although it is _not a part of this proposal_). This pattern is currently prevalent in the standard library, but they're not yet nested, because we couldn't nest structs inside protocols before.:
254
-
255
-
```swift
256
-
protocolCollection {
257
-
258
-
structSlice<Base: Collection>: Collection{ // generic param _could_ be implicit, hidden
associatedtypeSubSequence: Sequence=Slice<Self> // In the source of the capture, Slice would then imply Slice<Self>
266
-
}
267
-
268
-
let slice: Array<Int>.SubSequence=Collection.Slice<Array<Int>>(base: [1, 2, 3, 4, 5], bounds: 0..<2)
269
-
```
270
-
271
-
This would only work for would-be captures from the immediate parent, before we start having protocols capturing associated types. So it's best to leave this idea for now and approach it again when we have a more comprehensive solution.
276
+
By capturing associated types, the type `RandomAccessCollection.Concurrent` would also become existential (something like `RAC.Concurrentwhere Parent ==Array<Int>`). Consider if we mapped the capture of 'Parent' in to a generic parameter automatically (like `Concurrent` used to be, earlier in this document), but the compiler did that automatically. This kind of capturing between nesting types would be valuable, but it is _not a part of this proposal_. That is because it 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.
272
277
273
278
```swift
274
279
protocol Top {
@@ -277,11 +282,11 @@ Given that there is some friction between protocols with associated types ("gene
var result: Result { get } // implicit: Result<Self, ???> - would need to capture 'Self' from Parent
289
+
var result: Result { get } // implicit: Result<Self, ???> - would need to capture 'Self' from Parent
285
290
}
286
291
}
287
292
```
@@ -290,91 +295,22 @@ That's a long explanation of why it's best to just bar any kind of capturing bet
290
295
291
296
## Source compatibility
292
297
293
-
This change is mostly additive, although there are several places in the standard library where we can organise things better after this change. Specifically:
294
-
295
-
- The `FloatingPoint{Sign,Classification,RoundingMode}` enums will become members of the `FloatingPoint` protocol
296
-
- The `MirrorPath` protocolwill become a member of the `Mirror` struct, and renamed `Path`
297
-
298
-
- Lots of types in the global namespace will become nested to `IteratorProtocol`, `Sequence`, or some type of `Collection`
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:
Source migration can be handled with a typealiasand deprecation notice (with fixit), for example:
363
-
364
-
```swift
365
-
@deprecated("Use FloatingPoint.Sign instead")
366
-
typealiasFloatingPointSign=FloatingPoint.Sign
367
-
```
368
-
369
-
It is also 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 handled as above, with a deprecated typealias.
370
-
371
307
## Effect on ABI stability
372
308
373
-
Would change the standard library interface, platform SDK interfaces.
309
+
This proposal is only about the language feature, but it is likely to result instandard library and platform SDK changes.
374
310
375
311
## Effect on API resilience
376
312
377
-
Since all capturing is disallowed, this type of nesting would only change the name (both in source and symbolic) of the relevant types.
313
+
Since all capturing is disallowed, this type of nesting would only change the name (in source and symbolic) of the relevant types.
0 commit comments