Conditional Conformance with Protocol Inheritance

I have mistake with Conditional Conformance in this code.

struct Bee<Leaver> {
    let leaver: Leaver
}

protocol Breathable {
    func breath()
}
protocol Movable {
    func move()
}

protocol Leavable: Breathable, Movable {}


struct Human: Leavable {
    func breath() {}
    func move() {}
}

extension Bee: Leavable where Leaver: Leavable { // type 'Bee<Leaver>' does not conform to inherited  protocol 'Breathable'
    func breath() {
        leaver.breath()
    }
}

Please tell, what I do wrong?

I'm not sure why protocol Leavable: Breathable, Movable is not working, but a workaround you could do is to create a typealias.

typealias Leavable = Breathable & Movable

or conform seperately:

extension Bee: Breathable where Leaver: Breathable {
  func breath() {
    leaver.breath()
  }
}
extension Bee: Movable where Leaver: Movable {
  func move() {
    leaver.move()
  }
}

Not Bad. Thanks

I think this looks like a compiler bug. Note that:

protocol Fooable {
    func foo()
}
protocol Barable {
    func bar()
}
protocol Foobarable : Fooable, Barable {
}

// Conditionally conforming Array to Foobarable works as expected:
extension Array: Foobarable where Element: Foobarable {
    func foo() { forEach { $0.foo() } }
    func bar() { forEach { $0.bar() } }
}
// And it also works on eg Optional:
extension Optional: Foobarable where Wrapped: Foobarable {
    func foo() { self?.foo() }
    func bar() { self?.bar() }
}

// But, for some reason, it doesn't work on for example this:
struct MyArrayOfOne<Element> {
    let element: Element
}
extension MyArrayOfOne: Foobarable where Element: Foobarable { // <- Error
    func foo() { element.foo() }
    func bar() { element.bar() }
}
// Error: Type 'MyArrayOfOne<Element>' does not conform to protocol 'Barable'

Filed SR-7253

Agreed. @AlexSedykh do you want to file the bug report?

I edited my previous post to mention that I filed it, just before your post.

2 Likes

Now I think is not bug, this not realized feature, but I wonder that in stdlib not use it.

Did you test in on a beta version? Conditional conformances don't work at all for me since I have the regular Xcode (Swift 4.0.3). Maybe the author of this topic is under 4.2 as well?

I tested with Xcode 9.3 beta 4, default toolchain and also a recent dev snapshot.

1 Like

It is a known bug, resolved as duplicate of SR-6474

SR-6474 notwithstanding, I don't think this is a bug, although the error message certainly seems off-base.

Take a look at the implied conditional conformance section in the conditional conformance design doc, which is cited in SR-6474. It requires the "implied" conformances to be stated explicitly to forestall ambiguity of implementation. This topic is also being discussed in more detail in this thread.

It looks like Breathable and Movable don't actually need separate extensions. You can just name them in the same extension as for Leavable.

protocol Breathable {
    func breath()
}

protocol Movable {
    func move()
}

protocol Leavable: Breathable, Movable {}

struct Bee<Leaver> {
    let leaver: Leaver
}

extension Bee: Breathable, Movable, Leavable where Leaver: Leavable {
    func move() {
        leaver.move()
    }
    func breath() {
        leaver.breath()
    }
}

The code in the OP is missing an implementation of move() for Bee, so in fact it is not Leavable as shown. But I don't think the compiler ever got to that point. :slight_smile:

1 Like

But if it's not a bug, then it is a bug that it is possible (in recent versions of the compiler) to conditionally conform eg Array and Optional to Fooable (in my example program above).

EDIT: I meant Foobarable, not Fooable.

Aye. @jrose noted this in SR-6474 as well.

That bug report also says that explicitly mentioning the implied protocols doesn't always work. However, the sample code there seems to work fine for me in 9.3 beta 4, so perhaps that part is already fixed.

It sounds like there may be a couple of different bugs in play as well as the underlying design issue of how to handle implied conformances. But the upshot for the OP is that I don't think it's supposed to work to just extend directly to Leavable.

1 Like

Yes, so for completeness, here's how to conditionally conform MyArrayOfOne to Foobarable (leaving Array and Optional there to demonstrate -- what turned out to probably be -- the actual bug):

protocol Fooable {
    func foo()
}
protocol Barable {
    func bar()
}
protocol Foobarable : Fooable, Barable {
}

// Conditionally conforming Array to Foobarable works by just doing this:
extension Array: Foobarable where Element: Foobarable {
    func foo() { forEach { $0.foo() } }
    func bar() { forEach { $0.bar() } }
}
// Even though it probably shouldn't. The same goes for eg Optional:
extension Optional: Foobarable where Wrapped: Foobarable {
    func foo() { self?.foo() }
    func bar() { self?.bar() }
}

// But, as discussed above, in order to conditionally conform eg this:
struct MyArrayOfOne<Element> {
    let element: Element
}
// You have state each conformance explicitly:
extension MyArrayOfOne: Fooable where Element: Fooable {
    func foo() { element.foo() }
}
extension MyArrayOfOne: Barable where Element: Barable {
    func bar() { element.bar() }
}
extension MyArrayOfOne: Foobarable where Element: Foobarable {
}
// This compiles with default toolchain of Xcode 9.3 beta 4.

Not sure I follow here. Isn't the conformance redundant?

Yes, the extra ones are uninformative here and the compiler could allow you not to declare them explicitly. But that wouldn't work in all cases.

Near as I can tell, the heart of the issue is that you may not have more than one extension that conforms a type to a particular protocol. Otherwise it's ambiguous which versions of the protocol methods should be called when you invoke protocol methods on that type.

The "no implicit conformances" rule prevents you from inheriting multiple implementations of an "upstream" protocol by requiring all intermediate conformances to be explicitly declared. If they are found to duplicate existing conformances, the compiler can flag them at the point of declaration.

That other current thread goes into much more detail.