Swift / Xcode 14 - AnyObject type parameter requirement not satisfied by protocol derived from AnyObject

protocol P : AnyObject {}
class C<T: AnyObject> {}
let q = C<P>()

results in:

expression failed to parse:
error: AnyObjectProtocol.playground:5:9: error: 'C' requires that 'any P' be a class type
let q = C<P>()
        ^

AnyObjectProtocol.playground:4:7: note: requirement specified as 'T' : 'AnyObject' [with T = P]
class C<T: AnyObject> {}
      ^

This is really confusing since the following code seems to recognise that values of type P can be cast to AnyObject:

protocol P : AnyObject {}
class A: P{}
class B: P{}

let h: (AnyObject) -> () = { print($0) }
let f: (P) -> () = { h($0) }
f(A())
f(B())

And it's not like protocols couldn't be used as type parameters for generic classes

protocol P : AnyObject {}
class D: P {}

class G1<T> {}
class G2<T: AnyObject>{}

let k = G1<P>()
let m = G1<D>()
let n = G2<D>()
let z = G2<P>() // 'G2' requires that 'any P' be a class type

^ only the last line (with z) causes compilation error

Is it a compiler bug?

Replies

No, there's no bug here. When you use a protocol name as a type, it's an "existential" type, which in current Swift is written as any P. An existential type is a value type (that is, effectively, a struct rather than a class), which contains a value of a concrete type that conforms to the protocol.

That means you can't use any P to specialize a generic whose type parameter is required to be a reference type (a class or AnyObject).

You said:

the following code seems to recognise that values of type P can be cast to AnyObject

In fact, values of existential type any P can't be cast to AnyObject. Values of types conforming to P can be cast to AnyObject, though.

There seems to be some semantic disparity then between bridged and native notations.

@protocol ObjCProto <NSObject>
- (void)dummyFunc;
@end

@protocol ObjCProtoPropContainer <NSObject>
@property (nonatomic, strong, nullable, readonly) NSHashTable<id<ObjCProto>> *hashTable;
@end

are (according to Assistant/Counterparts/Swift 5 Interface) exposed to Swift as

public protocol ObjCProto : NSObjectProtocol {
    func dummyFunc()
}
public protocol ObjCProtoPropContainer : NSObjectProtocol {
    @available(macOS 10.5, *)
    var hashTable: NSHashTable<ObjCProto>? { get }
}

and allow for successful compilation of the following Swift code (placed in main file)

class Q1: NSObject, ObjCProtoPropContainer {
    let hashTable = Optional(NSHashTable<any ObjCProto>())
}

^ ✅ OK


In both cases ( @protocol ObjCProto and protocol P : AnyObject ) we do know that only reference types can conform to them.

So why can we cast any ObjCProto to AnyObject and can't do the same with any P ?

Is there maybe any specifier I could put on Swift-written protocol (P) to get the same treatment ?

Ok.

import Cocoa
@objc protocol P {}
class C<T: AnyObject> {}
let q = C<P>()

^ ✅ works fine.

So thank you for leading me to this workaround! 🙇‍♂️


But I am still confused on why it is

@objc protocol P {}

that makes it work instead of

protocol P : AnyObject {}