Wildchild9
(Wildchild9)
1
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!
Ben_Cohen
(Ben Cohen)
2
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.
Wildchild9
(Wildchild9)
3
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().
Ben_Cohen
(Ben Cohen)
4
edit: nope If L.T: B then L == L.T because that is what B requires
Wildchild9
(Wildchild9)
5
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?
Lantua
6
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
Ben_Cohen
(Ben Cohen)
7
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
Ben_Cohen
(Ben Cohen)
8
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
Ben_Cohen
(Ben Cohen)
9
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
Wildchild9
(Wildchild9)
10
So just to be clear, this is definitely a bug since the code seems to clearly be ignoring the Self == T constraint.
1 Like