Class-constrained protocol extension

I'm not sure if this rises to the level of a bug, or if it's just slightly off, or is working as intended. Consider a protocol extension defined like this:

protocol P {
    var isTrue: Bool { get set }
}
extension P where Self: AnyObject {
    func setTrue(_ value: Bool) {
        self.isTrue = value // <- error: "Cannot assign to property: 'self' is immutable"
    }
}

In one sense, this seems right because P is not class-constrained, and the function is not marked mutating. OTOH, it seems wrong, because the function is declared in an extension that "knows" that Self is a reference type.

Now let's try to "fix" this by marking the function as mutating:

extension P where Self: AnyObject {
    mutating func setTrue(_ value: Bool) {
        self.isTrue = value
    }
}
class A: P {
    var isTrue = false
}
let a = A()
a.setTrue(true) // error: "Cannot use mutating member on immutable value: 'a' is a 'let' constant"

The type of a is statically known to the compiler to be class A, which means the error message is at least misleading, but more plausibly flat out wrong.

Is there a bug here somewhere, or is this what should be happening?

(This is the version of Swift in Xcode 10.3.)

1 Like

I don't know about the first part, but the second part is not considered a bug because mutating methods on protocols can replace self.

1 Like

Oh, that's the behavior that lets us have pseudo-factory methods for classes? I never really got that before now.

Still, isn't a little fishy in the case where the extension is "class-constrained" by the where clause? A little bit paradoxical? Worthy of a warning?

There are a few previous threads on the topic:

(and possibly more...)

The fact that the class constraint is not inferred with a Self: ClassType constraint is going to be fixed by my PR shortly (see #27057). I don't think it will fix the above issue though (but feel free to file a bug if you think it should be fixed as well).

In the meantime, you need to either mark the setter as nonmutating or move the AnyObject constraint to the protocol directly.

2 Likes

Would “protocol ClassyP : class, P {} // extend this one” work? I’m not where I can check