[Second Review] SE-0427: Noncopyable Generics

This is greatly bothering me, as it feels like we somehow ended up with rules that are opposite to what the LSG asked for in their review notes:

  • Inference of Copyable requirements on conditional conformances to Copyable is confusing; requirements in this case should be made explicit.

The proposal was updated to reflect this:

We do this by declaring a conditional conformance:

extension List: Copyable where T: Copyable {}

Note that the where clause needs to be written, because a conformance to
Copyable declared in an extension does not automatically add any other
requirements, unlike other extensions.

This is all very good. The rules as described make for eminently readable code, as we have to explicitly spell out what we mean. These variants are both hard errors:

extension Optional: Copyable where Wrapped: ~Copyable {}
// error: cannot suppress '~Copyable' on generic parameter 'Wrapped' defined in outer scope
// error: generic enum 'Optional' required to be 'Copyable' but is marked with '~Copyable'
// error: associated value 'some' of 'Copyable'-conforming generic enum 'Optional' has non-Copyable type 'Wrapped'

extension Optional: Copyable {}
// error: generic enum 'Optional' required to be 'Copyable' but is marked with '~Copyable'
// error: associated value 'some' of 'Copyable'-conforming generic enum 'Optional' has non-Copyable type 'Wrapped'

All seems well on the release/6.0 branch. We only have a single one of these suppressible quasi-protocols (Copyable), and there appears to be only one way to spell conditional conformances to them, by explicitly spelling things out. We don't need to change anything there, and I think SE-0427 can be considered fully implemented there.


However, on main, we also (provisionally) have a second suppressible quasi-protocol, Escapable. I think the addition of this second protocol has uncovered an inconsistency in the current implementation, and this will need to be fixed. (On main only, as it only matters there.)

When I go and relax the escapability requirement on Optional, the meaning of the conditional Copyable conformance syntax suddenly changes in an unexpected way:

enum Optional<Wrapped: ~Copyable & ~Escapable>: 
  ~Copyable, ~Escapable 
{ ... }

extension Optional: Copyable 
where Wrapped: Copyable {}

extension Optional: Sendable 
where Wrapped: Sendable & ~Copyable {}

I've grown to assume that for generic type parameters, the omission of an explicit requirement on one of these suppressible protocols means an implicit positive conformance -- so I was very much surprised to find that their actual meaning is:

extension Optional: Copyable 
where Wrapped: Copyable & ~Escapable {} // !?

extension Optional: Sendable 
where Wrapped: Sendable & ~Copyable & Escapable  {} // OK!

Additionally, I cannot even avoid the confusion by spelling out precisely what I mean, as the above code produces the following error on the Copyable conformance declaration:

error: cannot suppress '~Escapable' on generic parameter 'Wrapped' defined in outer scope

I think this goes directly against the spirit of the LSG's request -- the review notes ask that "requirements in this case should be made explicit", but here we currently force them to remain implicit (and we also use a default that's inconsistent with the regular case).

I'd very strongly prefer if we required all conditional conformances to a suppressible protocol to explicitly spell out what their requirements are for all such protocols that the type suppressed on its original declaration.

extension Optional: Copyable where Wrapped: Copyable {}
// error: Hey dummy, you forgot to specify what you want for `Escapable`

extension Optional: Copyable where Wrapped: Copyable & ~Escapable {}
// Should be OK

extension Optional: Copyable where Wrapped: Copyable & Escapable {}
// Should also be OK

(The diagnostic would also make sense to be a warning rather than a hard error, although there is no source compatibility concern here. Ideally it would come with fix-its to help resolving it.)

This does not need to affect SE-0427, as we only have Copyable there; but I think it should be part of the ~Escapable proposal -- the implicit default choice is misleading/confusing in this context, and we should rather be forcing developers to make an explicit choice.

7 Likes