Compiler forgets some constraints of P within extension to P, known bug?

Here's a protocol:

/// A type with a "swapped dual".
/// ```
/// x.swapped.swapped == x
/// && x.a == x.swapped.b 
/// && x.b == x.swapped.a
/// ```
/// Note that `x` and `x.swapped` can be of different types,
/// and so can `x.a` and `x.b`.
protocol Swappable where
    Swapped: Swappable,
    Swapped.Swapped == Self,
    Swapped.A == B, Swapped.B == A
{
    associatedtype A
    associatedtype B
    associatedtype Swapped
    var a: A { get }
    var b: B { get }
    var swapped: Swapped { get }
}

We can make types conform to it, eg:

struct Pair<L, R> {
    var left: L
    var right: R
}
extension Pair : Swappable{
    var a: L { return left }
    var b: R { return right }
    var swapped: Pair<R, L> { return Pair<R, L>(left: right, right: left) }
}

and it will work as expected:

func foo() {
    let op = Pair(left: 123, right: "abc") // (Original Pair)
    let sp = op.swapped                    // (Swapped Pair)
    print(op.left, op.right) // 123 abc
    print(sp.left, sp.right) // abc 123
}
foo()

But, when writing extensions to Swappable, the compiler has forgotten some of the constraints, Swapped.A == B, Swapped.B == A, and the only way to make it compile is to restate them:

// This should compile according to constraints of Swappable, but does not:
extension Swappable
    // WORKAROUND: Uncomment these (restated) constraints:
    // where Swapped.A == B, Swapped.B == A
{
    func bar() {
        let _: Self = swapped.swapped // <- Compiles as expected.
        let _: Self.A = swapped.b // <- ERROR: Cannot convert value of type
                                  //           'Self.Swapped.B' to specified
                                  //           type 'Self.A'.
        let _: Self.B = swapped.a // <- ERROR: Cannot convert value of type
                                  //           'Self.Swapped.A' to specified
                                  //           type 'Self.B'.
    }
}

I guess this is a bug?

I would expect this to compile without the workaround, and I would kind of expect the workaround to result in a "redundant same type constraint". I think I've read something about this somewhere but couldn't find anything now.


Complete demonstration program here.
//----------------------------------------------------------------------------
// This program demonstrates (what I assume must be) a compiler bug.
// The compiler forgets some of the same type constraints of the protocol in
// protocol extensions. The issue can be worked around by restating those
// constraints at the extension.
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
// Compiling without the workaround (ie this program, as is):
//----------------------------------------------------------------------------
// $ swiftc --version
// Apple Swift version 5.1.3 (swiftlang-1100.0.282.1 clang-1100.0.33.15)
// Target: x86_64-apple-darwin19.2.0
// $ swiftc test.swift
// test.swift:43:33: error: cannot convert value of type 'Self.Swapped.B' to specified type 'Self.A'
//         let _: Self.A = swapped.b // <- ERROR: Cannot convert value of type
//                         ~~~~~~~~^
//                                   as! Self.A
// test.swift:46:33: error: cannot convert value of type 'Self.Swapped.A' to specified type 'Self.B'
//         let _: Self.B = swapped.a // <- ERROR: Cannot convert value of type
//                         ~~~~~~~~^
//                                   as! Self.B
//
// Same thing when using dev snapshot 2020-01-31, ie:
// Apple Swift version 5.2-dev (Swift a0fbeb9179)
// Target: x86_64-apple-darwin19.2.0
//
//----------------------------------------------------------------------------
// Compiling and running with the workaround (ie after uncommenting line 62):
//----------------------------------------------------------------------------
// $ swiftc test.swift && ./test
// 123 abc
// abc 123
//----------------------------------------------------------------------------


/// A type with a "swapped dual".
/// ```
/// x.swapped.swapped == x
/// && x.a == x.swapped.b
/// && x.b == x.swapped.a
/// ```
/// Note that `x` and `x.swapped` can be of different types,
/// and so can `x.a` and `x.b`.
protocol Swappable where
    Swapped: Swappable,
    Swapped.Swapped == Self,
    Swapped.A == B, Swapped.B == A
{
    associatedtype A
    associatedtype B
    associatedtype Swapped
    var a: A { get }
    var b: B { get }
    var swapped: Swapped { get }
}


// This should compile according to constraints of Swappable, but does not:
extension Swappable
    // WORKAROUND: Uncomment these (restated) constraints:
    // where Swapped.A == B, Swapped.B == A
{
    func bar() {
        let _: Self = swapped.swapped // <- Compiles as expected.
        let _: Self.A = swapped.b // <- ERROR: Cannot convert value of type
        //           'Self.Swapped.B' to specified
        //           type 'Self.A'.
        let _: Self.B = swapped.a // <- ERROR: Cannot convert value of type
        //           'Self.Swapped.A' to specified
        //           type 'Self.B'.
    }
}


struct Pair<L, R> {
    var left: L
    var right: R
}
extension Pair : Swappable{
    var a: L { return left }
    var b: R { return right }
    var swapped: Pair<R, L> { return Pair<R, L>(left: right, right: left) }
}

func foo() {
    // This works as expected (with or without the extension):
    let op = Pair(left: 123, right: "abc") // (op = Original Pair)
    let sp = op.swapped                    // (sp = Swapped Pair)
    print(op.left, op.right) // 123 abc
    print(sp.left, sp.right) // abc 123
}
foo()

Tested with the default toolchain of Xcode 11.3.1 (11C504) and
dev snapshot 2020-01-31.

Filed SR-12120.

2 Likes

I may be wrong, but it seems to me the problem lies here:

protocol Swappable {
  associatedtype A
  associatedtype B
  associatedtype Swapped: Swappable
  // Problem... Must be Swapped.Swapped == Swapped
    where Swapped.Swapped == Self,  
          Swapped.A == B,
          Swapped.B == A
  var a: A { get }
  var b: B { get }
  var swapped: Swapped { get }
}
  1. where Swapped.Swapped == Self
    Swappable.Swapped == Swappable
    Swappable.Swapped.B == Swappable.A // ?
    Swappable.B != Swappable.A // Where A == B? Only Swapped.B == A

  2. where Swapped.Swapped == Swapped
    Swappable.Swapped == Swappable.Swapped
    Swappable.Swapped.B == Swappable.A // ?
    Swappable.Swapped.B == Swappable.A // Its okay, coz Swapped.B == A

It looks like you have misunderstood something(s). To begin with Swappable is the protocol. and Swapped is one of its associated types, so you can't say:
"Swappable.Swapped == Swappable"

I don't think you understand me. This is not a compiler error, it is an incorrect type constraints. Read again carefully what I wrote. Yes, actually, with the correct restriction that I specified, the compiler does not swear. Ask yourself why

@Jens definitely smells like a bug because inside bar this swapped.a is B evaluates to true at runtime.

Edit: I'd add the missing constraint manually for now and a FIXME: When SR-12120 is resolved comment.

1 Like

I'd move the constrain from the protocol definition to the associated type requirement instead.

It doesn't change the issue though, this is personal preference only I think.

Are you sure? I don't have playgrounds available right now, but I was certain it worked as expected when the constraint was moved to the associated type? I might be mistaken.

I tested it by moving to the associated type yes, the result was the same for me. Also note that both forms should mean the same thing.

1 Like

Ah. I was mistaken then.

Yes, agreed, but I thought (incorrectly) that only one of them had the bug