Consider the following code:
public class Test<P: AnyObject> {
public func foo<T: P>(_ t: T.Type) -> T { // ERROR: Type 'T' constrained to non-protocol, non-class type 'P'
// stuff happens
}
}
Note the error on the second line, claiming that P
is not a class type. However, on line 1, P
is declared to extend AnyObject
, and therefore MUST be a class type. Therefore, the error is incorrect. ...Right? What's up with this code and/or compiler?
Edit: here is an example giving five similar generic functions. They are each measured against the functionality I want, and any comments in UPPERCASE note the ways in which they fail to meet my desires. To make the example slightly more concrete, I have replaced AnyObject
with a concrete class C0
. Note that C2
subclasses C1
subclasses C0
, and that CX
is unrelated.
public class C0 {
public required init() {
}
}
public class C1: C0 {
public required init() {
}
}
public class C2: C1 {
public required init() {
}
}
public class CX {
public required init() {
}
}
//public class D0<P: AnyObject> { // AnyObject replaced with C0 for a more concrete example
public class D0<P: C0> {
public func foo0<T: P>(_ t: T.Type) -> T { // DOESN'T COMPILE
return t.init()
}
public static func test_foo0() {
// let c2_0: C2 = D0<C1>().foo0(C2.self) // Function sig doesn't compile; can't test
// let cX_0: CX = D0<C1>().foo0(CX.self) // Function sig doesn't compile; can't test
}
// Shadows P in favor of its own local generic parameter P (T would accomplish the same result)
public func foo1<P>(_ t: P.Type) -> P { // TOO PERMISSIVE
return t.init() // Should compile ; does NOT compile
}
public static func test_foo1() {
let c2_1: C2 = D0<C1>().foo1(C2.self) // Should compile ; does compile
let cX_1: CX = D0<C1>().foo1(CX.self) // Should not compile ; DOES compile
}
public func foo2(_ t: P.Type) -> P { // TOO RESTRICTIVE
return t.init()
}
public static func test_foo2() {
let c2_2: C2 = D0<C1>().foo2(C2.self) // Should compile ; does NOT compile
let cX_2: CX = D0<C1>().foo2(CX.self) // Should not compile ; does not compile
}
// Hardcoded to match the constraint that is *on P*
public func foo3<T: C0>(_ t: T.Type) -> T { // TOO PERMISSIVE
return t.init()
}
public static func test_foo3() {
let c0_3: C0 = D0<C1>().foo3(C0.self) // Should not compile ; DOES compile
}
// Hardcoded to match the actual generic parameter of my example
public func foo4<T: C1>(_ t: T.Type) -> T { // HARDCODED TO MATCH MY SINGLE EXAMPLE
return t.init()
}
public static func test_foo4() {
let c2_4: C2 = D0<C1>().foo4(C2.self) // Should compile ; does compile
let c0_4: C0 = D0<C1>().foo4(C0.self) // Should not compile ; does not compile
let cX_4: CX = D0<C1>().foo4(CX.self) // Should not compile ; does not compile
}
}
The first example, foo0
, is what I'd expect to work, but it doesn't compile. In the fifth example, foo4
, I have hardcoded the generic parameter P
as C1
, the way it should resolve in D0<C1>
, what I use in each test. This works as expected, but is no longer generic.
I assert that foo0
should compile, and (under D0<C1>
) have the same compile-time behavior as foo4
.
Therefore, the error is incorrect. ...Right?
Well... the current constraints system is not yet powerful enough to allow this kind of constraints. The good news is that generalized supertype constraints is on the Swift road map as described in the Generics Manifesto.
The below won't compile either, for the same reasons:
func test<A, B>(_ a: A, _ b: B) where A: AnyObject, B: A {
// ^^^ Type 'B' constrained to non-protocol, non-class type 'A'
}
Neither the example from the manifesto:
protocol P {
associatedtype Base
associatedtype Derived: Base
}
Unfortulately, you'll have to wait until this feature is available to Swift, to make it work as you want.