Superclass constraint is recursive

I'm a little confused about this behavior. The following non-generic code is allowed:

protocol Test {}

extension Test where Self: A {}

class A: Test {}

A generic version however is resulting in a compiler error:

protocol Test {
  associatedtype T

// error: superclass constraint 'Self' : 'A<Self.T>' is recursive
extension Test where Self: A<T> {}

class A<T>: Test {}
  • Is this intended or a bug?
  • Any idea how to workaround it?

I'm trying to create a single extension on a view type around a final generic class which has no super-class. I also want to avoid duplicating code for each possible generic parameter of that generic class. Therefore I added a base protocol which I use to lookup the generic parameter. The issue is that I also need to access some internal members, but for that I need the above constraint to work instead of adding the members directly as protocol requirements. If I did so it would expose everything to the public, which is a no-go.

cc @Slava_Pestov

This is intended for now. Self: A<T> is shorthand for Self: A<Self.T> and you can see that the subject type (Self in this case) occurs on the right hand side of the superclass constraint.

The same restriction exists for same-type constraints. I think it would be possible to lift this restriction for superclass constraints, at least in some cases. However we haven't thought through the implications. It might also run into implementation restrictions. So for now I suggest redesigning your API instead of holding out hope for a language change here.

1 Like

FWIW, I've bumped up against this with same-type constraints a bunch of times. In every case, I the intent is to constrain the type to be a specific generic type which conforms to the protocol while letting the type arguments remain unconstrained. I wonder if there would be a way to support that use case one way or another someday...

The problem with same-type constraints is that it can cause us to construct infinite types. For example if your constraint says T == Array<T.Element> and you substitute in a concrete type for T where Element is equal to the type itself, then T must be Array<Array<Array<Array<...>>>>.

1 Like

So is there no workaround or what?

Ahh, that makes sense. This problem comes up often enough that maybe some other kind of syntax such as T: Array could address the use cases without bumping into that degenerate case.

Does it mean, that there is no way to make a protocol only applicable to a generic class, i.e.

class A<T> {}

protocol Test where Self: A<???> {}

Not with this syntax. But you can use another technique, which involves the definition of an extra helper protocol:

class A<T> {}

// Helper protocol
protocol AProtocol {
    // declare here all A methods you want to use from Test

// Have A conform to the helper protocol
extension A: AProtocol { }

// Now you can declare a constrained Test protocol
protocol Test where Self: AProtocol {

It works, but in my case it would mean that I need to expose the protocol and some implementation details to the library user. I had to redesign a few things to avoid exposing internal API and ended up with a surface where only some internal types were exposed but not their members. That said, I still think this would be a very handy generic feature to have and I'd love to see it sorted out one day. :slight_smile:

Thanks for the advise. The main problem for me is that a helper protocol cannot be parameterised, i.e. to apply this approach I need to extract a "non-parameterised" part of my generic class and use it in the "Test" protocol constraint, but this is unfortunately not possible. Will it be possible to use generic classes in constraints in the future versions ?

You can add associatedtype T to the protocol and then you‘ll be able to extract T in the extension.

The protocol constraint won’t compile though, which is the main issue of this thread.

Oh. I see, thanks. But yeah, I'm not sure whether it works for me, the idea is to say: to conform to "Test" you need to subclass from A, that comes from another library, using this solution it would be: to conform to "Test" your class need to conform to "AProtocol" but actually what I need is to let a user to subclass from A in order to use "Test".

Terms of Service

Privacy Policy

Cookie Policy