Problem in extensions of protocols with associatedtypes

These protocols will apply to both code snippets

protocol P {}

protocol P1: P {}

This works perfectly fine:

func foo1<T: P1>(_ arg: T) {
    
    foo2(arg)
}

func foo2<T: P>(_ arg: T) {}

However, the following extension won't compile, apparently because the compiler doesn't recognize that the associated types became related once constrained.

protocol Foo {
    
    associatedtype Type1: P1
    associatedtype Type2: P
    
    func foo1(_ arg: Type1)

    func foo2(_ arg: Type2)
}

extension Foo {
    
    func foo1(_ arg: Type1) {
        
        self.foo2(arg)
    }
    
    func foo2(_ arg: Type2) {}
}

If this isn't an imperfection and is intentionally done, I would be glad to know the reasons.

P.S. The same issue is present in an analogous generic class.

Type1 can be any type conforming to P1 and Type2 can be any type conforming to P. There's no subtyping relationship between Type1 and Type2. In fact, if Type1 and Type2 are classes, either Type1 could be superclass of Type2 or vice versa.

1 Like

That is true in terms of inheritance. But it shouldn't matter as long as they both conform to P.

As in the example with generic functions, for instance.

In your example with protocols, foo1 doesn't accept an argument of any type that conforms to P1; it only takes an argument of type Type1. Likewise with foo2 as it relates to P and Type2. There is no subtyping relationship between Type1 and Type2, so there's no covariance to speak of. Associated types aren't just another way of spelling generics or existentials.

To give a very simple example: String and Int both conform to Equatable, and Int conforms to Comparable. Let's look at your example:

protocol Foo {
  associatedtype Type1: Comparable
  associatedtype Type2: Equatable
  func foo1(_ arg: Type1)
  func foo2(_ arg: Type2)
}

struct S {
  typealias Type1 = Int
  typealias Type2 = String
}

extension S: Foo {
  func foo1(_ arg: Type1) {
    self.foo2(arg)
    // Of course I can't do this for S.
    // So why would I be able to do this in an extension for Foo?
  }
  func foo2(_ arg: Type2) { }
}
3 Likes

Right. I got you, thanks.
It really doesn't make sense for concrete types. But it seems like it does make sense if I could pass only protocols or only concrete types as generic parameters or associated values. I was going to pass a protocol, which is the reason I didn't realize my mistake.