Class-bound protocol refinement in Swift

Why does this code throw a compile error?

protocol P { var i: Int { get set } }
protocol ClassP: P, AnyObject {}
class C: ClassP { var i: Int = 0 }

let classP: ClassP = C()
classP.i = 1   //  Cannot assign to property: 'classP' is a 'let' constant

But all is fine if I move the AnyObject restriction to P:

protocol P: AnyObject { var i: Int { get set } }
protocol ClassP: P {}
class C: ClassP { var i: Int = 0 }

let classP: ClassP = C()
classP.i = 1

Note how classP is typed as ClassP, and ClassP is class-bound in both examples. But the compiler seems to notice that the assignment targets the property of a class instance (as opposed to a struct instance) only if the property is declared in a protocol that itself is already class-bound. The static type of classP seems to get ignored. Why?

Ideally P itself shouldn't be class-bound, as it could easily have non-class-bound refinements and conforming types. Of course in those cases a p.i = 1 assignment would only be valid with a writeable p. With a let-const p, p.i = 1 should only be valid if p is known to be an instance of a conforming class (which I'd think is true in the above example, because the known type is ClassP and that is indeed class-bound). How do I express this in Swift?

1 answer

  • answered 2021-05-17 17:57 AnderCover

    Not pretty but adding i as a requirement of ClassP silence the error:

    protocol P { var i: Int { get set } }
    protocol ClassP: P, AnyObject { var i: Int { get set }  }
    class C: ClassP { var i: Int = 0 }
    
    let classP: ClassP = C()
    classP.i = 1