Skip to content

Commit 546c089

Browse files
committed
Draft 2
1 parent 4c8612d commit 546c089

File tree

1 file changed

+95
-25
lines changed

1 file changed

+95
-25
lines changed

proposals/NNNN-suppressed-associated-types.md

Lines changed: 95 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ after building the declaration's generic signature, if there was an inverse
187187
requirement `Thing: ~IP` and yet `Thing` must conform to `IP` anyway, then the
188188
inverse requirement is diagnosed as invalid.
189189

190+
### Limits of Suppression
191+
190192
Default requirements become fixed once the generic signature is built for that
191193
declaration, after applying the expansion procedure. For example,
192194

@@ -218,6 +220,43 @@ inverse requirement `Val.Element: ~Copyable` without mutating the generic
218220
signature of `Stack`, which is not permitted, so the inverse requirement is
219221
illegally scoped in `push`.
220222

223+
The same concept applies to the requirement signatures of a protocol becoming
224+
fixed after expansion is applied to it locally. Consider this protocol `P`,
225+
226+
```swift
227+
protocol P<A>: ~Copyable {
228+
associatedtype A: ~Copyable
229+
}
230+
```
231+
232+
Its requirement signature is `<Self where Self : Escapable, Self.A : Escapable>`,
233+
because the default `Copyable` requirements were suppressed on both `Self` and
234+
`Self.A`. Next, consider this protocol `Q`,
235+
236+
```swift
237+
protocol Q<B>: ~Copyable {
238+
associatedtype B: ~Copyable, P
239+
}
240+
```
241+
242+
The expansion procedure applies locally to `Q` as follows.
243+
The desugared requirement `Self.B: P` implies `Self.B.A: Copyable`, yielding the now-fixed requirement signature:
244+
245+
```
246+
<Self where Self : Escapable, Self.B : P, Self.B.A : Copyable>
247+
~~~~~~~~~~~~~~~~~~~
248+
from expansion procedure
249+
```
250+
251+
Constraining a generic type parameter `T` to conform to `Q` only permits suppression of a default inferred for `T.B`, not `T.B.A`:
252+
253+
```swift
254+
func limits<T: Q>(_ t: T)
255+
where T.B: ~Copyable,
256+
T.B.A: ~Copyable // error: T.B.A is required to be Copyable
257+
{}
258+
```
259+
221260
Inverses can apply across equality constraints _within the same declaration's generic signature_
222261
to cancel-out default requirements from primary associated types. Consider this example,
223262

@@ -417,17 +456,20 @@ func ex2<R: ~Copyable>(_ s: any Source<R>) {
417456

418457
### Recursion
419458

420-
Protocol requirements can be such that an infinite number of associated types
421-
are derivable from a type conforming to that protocol. A classic example is,
459+
There can be an infinite number of type parameters derivable from a conformance
460+
requirement, because a protocol's associated type requirement can be part of a cycle with the protocol itself:
422461

423462
```swift
424463
protocol P<A>: ~Copyable {
425464
associatedtype A: ~Copyable, P
426465
}
427466
```
428467

429-
For a generic environment `<R where R: P>`, all of `R.A, R.A.A, R.A.A.A, ...`,
430-
are Copyable because the type with one fewer `A` conforms to `P`.
468+
For a generic signature `<R where R: P>`, all of the type parameters
469+
`R.A, R.A.A, R.A.A.A, ...`,
470+
are Copyable.
471+
For any type parameter `X` rooted in `R`, the type `X.A` conforms to `P`, and by the expansion procedure, that implies `X.A: Copyable` because `A` is a primary
472+
associated type of `P`.
431473

432474
Next, consider this pair of mutually recursive protocols where only one of
433475
them has a primary associated type,
@@ -442,25 +484,21 @@ protocol Second: ~Copyable {
442484
}
443485
```
444486

445-
For a generic environment `<T where T: First>`, we observe that any archetype
446-
rooted in `T` and ending with an `A`, such `T.A.B.A`, is Copyable because
447-
`T.A.B: First` and `First` has a primary associated type `A`.
448-
Similarly, for an archetype that ends in a `B`, it is not Copyable, because
449-
`T.*.A: Second` which has only the ordinary associated type `B`. So we get an
450-
alternating pattern for the defaults,
487+
For a generic signature `<T where T: First>`, we observe that any type parameter
488+
rooted in T and ending with an A, such T.A.B.A, is Copyable because
489+
`T.A.B: First` and First has a primary associated type A.
490+
Similarly, for type parameter T.A.B which ends in a B, it is *not* required to conform to Copyable, because `T.A: Second` and Second has only
491+
the non-primary associated type B. So there is an alternating pattern for the
492+
defaults,
451493

452494
```
453495
T.A : Copyable
454-
T.B : ~Copyable
455-
T.B.A: Copyable
456-
T.B.A.B: ~Copyable
457-
T.B.A.B.A: Copyable
496+
T.A.B: ~Copyable
497+
T.A.B.A: Copyable
498+
T.A.B.A.B: ~Copyable
458499
...
459500
```
460501

461-
The proof by induction is left as an exercise for the reader.
462-
463-
464502
### Default Witnesses
465503

466504
An associated type can already declare a default _witness_, which is a type that
@@ -583,12 +621,13 @@ may now suppress conformance to these protocols, a conditional
583621
conformance to `Copyable` or `Escapable` that depends on an
584622
associated type is still not allowed:
585623
```swift
586-
struct QueueHolder<Q: Queue>: ~Copyable {}
587-
extension QueueHolder: Copyable where Q.Element: Copyable {} // error
624+
protocol Goose: ~Copyable { associatedtype Quack: ~Copyable }
625+
struct Pond<G: Goose>: ~Copyable {}
626+
extension QueueHolder: Copyable where G.Quack: Copyable {} // error
588627
```
589-
This restriction is for runtime implementation reasons.
628+
This restriction is for runtime implementation limitations.
590629
591-
<!-- TODO: Perhaps this needs elaboration? -->
630+
<!-- TODO: Perhaps the limitation needs elaboration? -->
592631
593632
594633
## Source Compatibility
@@ -635,8 +674,24 @@ assumption is no longer true.
635674
The ABI of existing code is not affected by this proposal.
636675
637676
On the other hand, changing an associated type declaration in an library
638-
to suppress conformance is an ABI-breaking change, for similar reasons
639-
to those described above.
677+
to suppress conformance is can be an ABI-breaking change. For example, an
678+
extension of a protocol providing a default implementation could have its symbol
679+
name change, as these two implementations of `greet` must have distinct names:
680+
681+
```swift
682+
protocol Greeter<T> {
683+
associatedtype T: ~Copyable
684+
func greet()
685+
}
686+
687+
extension Greeter {
688+
func greet() { print("hello")}
689+
}
690+
691+
extension Greeter where T: ~Copyable {
692+
func greet() { print("سلام") }
693+
}
694+
```
640695
641696
## Future Directions
642697
@@ -660,10 +715,25 @@ constrained existential via `any Q<some ~Copyable>`.
660715
661716
## Alternatives Considered
662717
718+
Through the development of this proposal, various alternate formulations were considered.
719+
663720
### No recursion
664721
665-
A prior version of this proposal [was pitched](https://forums.swift.org/t/pitch-suppressed-default-conformances-on-associated-types/81880) that was absent of any defaulting behavior
666-
for associated types.
722+
A prior version of this proposal [was pitched](https://forums.swift.org/t/pitch-suppressed-default-conformances-on-associated-types/81880) that was absent of any defaulting behavior for associated types. The primary fault was that it
723+
provided an inconsistent behavior when compared with generic types like S:
724+
725+
```swift
726+
struct S<T: ~Copyable>: ~Copyable {}
727+
728+
protocol P<T>: ~Copyable {
729+
associatedtype T: ~Copyable
730+
}
731+
732+
extension S {} // T: Copyable
733+
extension P {} // T: ~Copyable
734+
```
735+
736+
Only the extension for S provides a default for its T.
667737
668738
### Definition-driven associated type defaults
669739

0 commit comments

Comments
 (0)