Reputation: 3261
protocol ProtocolA {
func someFunc() -> Any
}
protocol ProtocolB: ProtocolA {
var someVar: Any { get }
}
enum MyEnum<T: ProtocolA, U: ProtocolB> {
case A(T)
case B(U)
}
protocol DataObject {
...
}
extension DataObject where Self: ProtocolB {
func doSomething() {
let x = MyEnum.B(self)
/// Compiler error:
/// Cannot invoke 'B' with an argument list of type '(Self)'
}
}
I don't understand why the above is giving me an error. Strangely, removing either of the two enum generic constraints, leaving the enum with a single constraint, solves the problem...
Note that ProtocolB extends ProtocolA. Is this not supported when used with generic constraints?
UPDATE
Changing MyEnum
to this solves the problem:
enum MyEnum {
typealias T = ProtocolA
typealias U = ProtocolB
case A(T)
case B(U)
}
However, I still don't quite get why...
Upvotes: 1
Views: 1101
Reputation: 299275
The error is a little misleading. Let's simplify the problem to explore it.
protocol ProtocolA {}
protocol ProtocolB: ProtocolA {}
enum MyEnum<T: ProtocolA, U: ProtocolB> {
case A(T)
case B(U)
}
struct AThing: ProtocolA {}
struct BThing: ProtocolB {}
let bThing = BThing()
let thing = MyEnum.B(bThing) // error: cannot invoke 'B' with an argument list of type '(BThing)'
So now we have the same problem without needing DataObject
. Why does this fail?
MyEnum
is generic. That means that in order to create a concrete type, it must know the types of T
and U
. You provided constraints on those types (one conforms to ProtocolA
and the other to ProtocolB
), but you didn't say specifically what types they are.
When we get to this line:
let thing = MyEnum.B(bThing)
What type is thing
? With type inference, we can work out what U
is, but what is T
?
let thing: MyEnum<?, BThing> = MyEnum.B(bThing)
There's just not enough context here to work it out, so we have to tell the compiler explicitly:
let thing = MyEnum<AThing, BThing>.B(bThing)
So the full type of thing
is MyEnum<AThing, BThing>
, which is a different type than MyEnum<OtherAThing, BThing>
. (Exactly like [Int]
is different type than [String]
, which is why let xs = []
won't compile without an explicit type definition.)
Your second example that works is not generic, so there's no problem. It simplifies to:
enum MyEnum {
case A(ProtocolA)
case B(ProtocolB)
}
Bound typealiases just rename types, they don't create new types (like an unbound typealias would, or as Swift 2.2 calls it, associatedtype
). So we know that A
takes any ProtocolA
and B
takes any ProtocolB
, and all MyEnum
instances have the same type.
The error is unfortunate because the compiler is confused. You'd get a clearer error if you simplified this down to the most basic case. This fails for exactly the same reason as your example.
enum MyEnum<T> {
case A
}
let x = MyEnum.A // error: generic parameter 'T' could not be inferred
Upvotes: 3