Conditional Conformance Problems

Conditional Conformance Problems

I was writing code earlier to make Array conform to CustomPlaygroundDisplayConvertible when it's elements are of type CGPoint. I wanted to make a different conformance for an Array when its elements are of type Optional<CGPoint> and ran into the following problem:

extension Array: CustomPlaygroundDisplayConvertible where Element == Optional<CGPoint> {
    //...
}

// Error: Conflicting conformance of 'Array<Element>' to protocol 'CustomPlaygroundDisplayConvertible'; there cannot be more than one conformance, even with different conditional bounds
extension Array: CustomPlaygroundDisplayConvertible where Element == CGPoint {
    //...
}

I haven't a clue why this is happening, aren't these different types?

Hello,

Multiple conditional conformances are currently not allowed: swift-evolution/0143-conditional-conformances.md at master · apple/swift-evolution · GitHub

In your case it looks innocuous, but there are many possible traps, as explained in the previous link. See also those related threads, for more context and information:

You may be able to get the result you're looking for by doing something like this.

extension Array: CustomPlaygroundDisplayConvertible { }

extension Array where Element == Optional<CGPoint> {
    // CustomPlaygroundDisplayConvertible implementation goes here
}

extension Array where Element == CGPoint {
    // CustomPlaygroundDisplayConvertible implementation goes here
}

extension Array {
    // Default CustomPlaygroundDisplayConvertible implementation goes here
}
1 Like

If you do that, Array’s conformance will be with the default method. The others will only ever be called if they happen to be an available static overload in a particular context. Namely:

let point = CGPoint(x: 1, y: 2)

// This will use the more‐specific overload.
print(array.playgroundDescription)

func demonstrate<T>(_ array: [T]) {
    // But this will only ever use the default implementation...
    print(array.playgroundDescription)
}
demonstrate(CGPoint(point)) // ...even when you probably want the specific one.

Instead, try to design it like this:

protocol CustomArrayElementPlaygroundDisplayConvertible {
    // Declare whatever options you’ll use here.
    var somethingRelevant: String { get }
}

extension Array where Element : CustomArrayElementPlaygroundDisplayConvertible {
    var playgroundDescription: Any {
        // Construct something using CustomArrayElementPlaygroundDisplayConvertible.
        return map({ $0.somethingRelevant }).joined()
    }
}

extension CGPoint : CustomArrayElementPlaygroundDisplayConvertible {
    // Conform...
}
extension Optional : CustomArrayElementPlaygroundDisplayConvertible
where Wrapped : CustomArrayElementPlaygroundDisplayConvertible {
    // Conform...
}

But you may want to do that on a wrapper instead of directly on Array, because there are pitfalls to creating a conformance of a protocol you do not control to a type you do not control.