Errors When Working with Generic Types

I have been working on some generic code recently and encountered two issues. Here is a generalized version of the code:

protocol B: A where T == Self { }

extension B {
    func bar() -> [T]? {
        return [self]
    }
}

protocol A {
    associatedtype T: B
    func bar() -> [T]?
}

struct C<L, R>: A where L: A, R: A, L.T == R.T {
    var l: L
    var r: R
    typealias T = L.T
    
    func bar() -> [T]? {
        if let x = l.bar() {
            return x
        } else if let y = r.bar() {
            return y
        }
        return nil
    }
}

The above code builds like one would expect.

My first problem arose when I tried to add another conformance to B. I changed its declaration line to the following.

protocol B: A, CaseIterable where T == Self { } 

After doing this, for some reason on the line where I try to return y, I now get the error "Type of expression is ambiguous without more context". This is super confusing to me as I cannot understand why adding the requirement that B also be CaseIterable would cause Swift not to know the type of y.

Secondly, whenever I try to create a type to conform to B, as shown in the code below, I receive two error.

// Error: Type 'Foo' does not conform to protocol 'A'
// Error: Type 'Foo' does not conform to protocol 'B'
enum Foo: B { }  

If I add to Foo the line typealias T = Self, it builds properly, but I don't understand why Swift doesn't already know this as I state it in the definition of B. This problem can also be remedied by removing where T == Self in the declaration of B and adding typealias T = Self in the extension on B, but this produces an annoying warning that says "Typealias overriding associated type 'T' from protocol 'A' is better expressed as same-type constraint on the protocol" with a fix-it for its suggestion. Although when I click the fix-it, it changes it back to what I had originally and pops up the error for Foo again.

Any help to these problems would be appreciated. Thank you!

I’m not so sure I do expect that code to build.

Let’s simplify things a little by getting rid of the bar() method:

protocol B: A where T == Self { }
protocol A {
    associatedtype T: B
}

struct C<L, R>: A where L: A, R: A, L.T == R.T {
    var l: L
    var r: R
    typealias T = L.T
}

C: A requires C.T: B. This means for typealias T to work, you need L.T: B. B requires T == Self. So L.T == L. Same goes for R.T.

Which is to say, the constraint L.T == R.T on C is the same as saying L == R, which is pointless and the compiler should tell you so:

struct S<L,R> where L == R { }
// error: Same-type requirement makes generic parameters ‘L’ and ‘R’ equivalent

And indeed, if you update the constraints on L and R to require B Instead of A, you get this error. Alternatively, you could try and define a concrete C and get a similar error:

struct SA: B { typealias T = SA }
struct SB: B { typealias T = SB }

let c: C<SA,SB>
// error: ‘C’ requires the types ‘SA.T’ (aka ‘SA’) and ‘SB.T’ (aka ‘SB’) be equivalent

So, I think the original code is malformed, but managed to slip through the compiler. Adding the CaseIterable stirs up this nonfunctional state a bit, getting it to spit out some other errors.

I don't think that it is correct that L.T == R.T is the same as saying L == R. You stated that L.T == L which is not true since L conforms to A, not B. I think it would be true to say though that L.T.T == L.T since L.T: B. In the actual example that this corresponds to L and R have different types all around my code, but C, L, and R do all share the same T value. And also since T: B, T also has the same T value, that is to say that T == T.T. I've written the rest of my code with the structure I initially outlined successfully when I removed the requirement that B also be CaseIterable, but when I add it in, it still creates the error highlighted above in C.bar().

edit: nope If L.T: B then L == L.T because that is what B requires

In the following example, L.T and L are different types, but L.T and L.T.T are always the same type. As well L does not equal R, but as stipulated L.T == R.T == C.T.

protocol B: A where T == Self { }
extension B {
    func bar() -> T {
        return self
    }
}
protocol A {
    associatedtype T: B
    func bar() -> T
}

struct C<L, R>: A where L: A, R: A, L.T == R.T {
    var l: L
    var r: R
    typealias T = L.T
    
    func bar() -> T {
        return [l.bar(), r.bar()].randomElement()!
    }
}
struct D<P>: A where P: A {
    var p: P
    typealias T = P.T
    
    func bar() -> P.T {
        return p.bar()
    }
}
struct E<P>: A where P: A {
    var p: P
    typealias T = P.T
    
    func bar() -> P.T {
        return p.bar()
    }
}
enum Foo: B {
    typealias T = Self
    case a
}

let thing = C(l: E(p: D(p: Foo.a)), r: Foo.a)
print("C == \(type(of: thing))") // Type of C
print("C.T == \(type(of: thing.bar()))") // Type of C.T
print("C.T.T == \(type(of: thing.bar().bar()))") // Type of C.T.T
print("L == \(type(of: thing.l))") // Type of L
print("L.T == \(type(of: thing.l.bar()))") // Type of L.T
print("L.T.T == \(type(of: thing.l.bar().bar()))") // Type of L.T.T
print("R == \(type(of: thing.r))") // Type of R
print("R.T == \(type(of: thing.r.bar()))") // Type of R.T
print("R.T.T == \(type(of: thing.r.bar().bar()))") // Type of R.T.T

// Prints:
// C == C<E<D<Foo>>, Foo>
// C.T == Foo
// C.T.T == Foo
// L == E<D<Foo>>
// L.T == Foo
// L.T.T == Foo
// R == Foo
// R.T == Foo
// R.T.T == Foo

As you can see in this demonstration, for all the types *.T == *.T.T, although, * does not necessarily equal *.T, except when *: B (which it does in the case of Foo and subsequently R). Is there something wrong with this code @Ben_Cohen?

A much smaller example:

protocol A {
  associatedtype T: CaseIterable
}

extension A where Self == T {
  // Error: Cannot convert return expression
  // of type 'Self' to return type 'Self.T'
  func bar() -> T { self }
}

Looks like all that's needed is the T: CaseIterable. I tried Equatable and Hashable so PAT is probably not the problem here.

Note:

Ran on Swift Playground (v. 3.3.1), so Swift 5.1.

1 Like

Yeah, I was mixed up about the B self constraint.

Here's a different reduction:

protocol B: CaseIterable { }
protocol A {
    associatedtype T: B
    func bar() -> T
}

struct C<L: A, R: A>: A where L.T == R.T {
    var l: L
    var r: R
    typealias T = L.T
    
    func bar() -> T {
      r.bar()
    }
}

Which prints "error: cannot convert return expression of type 'R.T' to return type 'C<L, R>.T' (aka 'L.T')", which directly contradicts the constraint.

Whatever it is, it does seem specific to something about CaseIterable.

1 Like

Here's @Lantua's version replacing CaseIterable with a simpler protocol...

public protocol P {
    associatedtype T: Sequence where Self == Self.T.Element
}

protocol A {
  associatedtype T: P
}

extension A where Self == T {
  // Error: Cannot convert return expression
  // of type 'Self' to return type 'Self.T'
  func bar() -> T { self }
}
1 Like

And here it is fully contained:

protocol I {
  associatedtype E
}

protocol S {
    associatedtype E where Self.E == Self.It.E
    associatedtype It: I
}

protocol P {
    associatedtype T: S where Self == Self.T.E
}

protocol A {
  associatedtype T: P
}

extension A where Self == T {
  // Error: Cannot convert return expression
  // of type 'Self' to return type 'Self.T'
  func bar() -> T { self }
}
2 Likes

So just to be clear, this is definitely a bug since the code seems to clearly be ignoring the Self == T constraint.

1 Like
Terms of Service

Privacy Policy

Cookie Policy