Testing Protocol Conformance with Associated Types

How to check conformance to protocol with associated type in Swift?

TL;DR
Compiler does not have enough information to compare the type until associated type is set.


When you refer to simple protocol, compiler knows its type from the beginning.
But when you refer to protocol with associated type, compiler doesn't know its type until you declare it.

protocol ExampleProtocol {
associatedtype SomeType
func foo(param: SomeType)
}

At this moment for compiler it looks like this:

protocol ExampleProtocol {
func foo(param: <I don't know it, so I'll wait until it's defined>)
}

When you declare a class conforming to the protocol

class A: ExampleProtocol {
typealias SomeType = String
func foo(param: SomeType) {

}
}

Compiler starts to see it like this:

protocol ExampleProtocol {
func foo(param: String)
}

And then it is able to compare types.

Protocol with associatedtype conformance with generic gives compiler error

It doesn't conform because you declared another Error type inside the class, so everywhere where you use Error in the required methods, it uses DBClientMock.Error instead of the protocol-required Swift.Error.

Either rename DBClientMock.Error to something else, or change the Error in methods to Swift.Error, like below:

// Extension
func get(id: UUID, completion: @escaping (T?, Swift.Error?) -> Void) {
//...
}

func insert(_ model: T, completion: @escaping (Swift.Error?) -> Void) {
//...
}

func delete(_ model: T, completion: @escaping (Swift.Error?) -> Void) {
//...
}

Protocol conformance of system classes using more generic types

The problem occurs due to the fact that Swift is covariant in respect to closure return type, and contra-variant in respect to its arguments. Which means that (VNRequest, Error?) -> Void can't be used where (Request, Error?) -> Void is needed (the other way around is possible).

You can solve your problem by using an associated type in the Request protocol:

public protocol Request {
associatedtype RequestType = Self
var results: [Any]? { get }
var completionHandler: ((RequestType, Error?) -> Void)? { get }
}

The above protocol definition will make the VNRequest class compile, as the compiler will detect the match for RequestType.

The downside, though, is that protocols with associated types have some limitations regarding where they can be used, and also passing them as function arguments will require some where clauses to make them work.


Another alternative would be to use Self as a parameter to the completion handler:

public typealias RequestCompletionHandler<T> = (T, Error?) -> Swift.Void

public protocol Request {
var results: [Any]? { get }
var completionHandler: RequestCompletionHandler<Self>? { get }
}

This will also solve the conformance issue, but also comes with some constraints: VNRequest must be final, the functions using Request must be generic:

func send<R: Request>(_ request: R)

Access associated type of a custom protocol in a where clause on generic types in Swift

The problem is that you're using the reserved word Type. Try it with some other name like HashType and it compiles fine.

See "Metatype Type" from the Swift Programming Language:

The metatype of a class, structure, or enumeration type is the name of that type followed by .Type.

You should probably get a compiler error on the typealias line rather than the class line, and you may want to open a radar on that.

Is there information somewhere on Swift protocol associatedtype using `=` versus `:`?

The = and the : are two independent parts of an associated type declaration, rather than being mutually exclusive. This is the full syntax of protocol associated type declaration:

attributes(opt) 
access-level-modifier(opt)
'associatedtype'
typealias-name
type-inheritance-clause(opt)
typealias-assignment(opt)
generic-where-clause(opt)

The : TypeName part is the type-inheritance-clause, and the = TypeName is the typealias-assignment.

: TypeName constrains what type the associated type can be, namely that it must inherit/conform to TypeName. This is why : SomeManagerDelegate didn't work in your case. You are saying that SomeManager.DelegateType must be some kind of SomeManagerDelegate, but for Manager, this is not true - Manager.delegate is of type ManagerDelegate, which is a totally unrelated protocol. Even if it were SomeManagerDelegate, it wouldn't work either because protocols don't conform to themselves.

= TypeName sets a default type for the associated type. If the compiler cannot infer what the type the associated type for a conformance should be and you didn't say it explicitly it either, it will use that type instead. But in your case, this fact didn't really matter. What actually caused your code to work, wasn't the addition of = SomeManagerDelegate, but the removal of the constraint : SomeManagerDelegate. You are no longer constraining what type the associated type should be (it can be anything!), so for Manager, the associated type can now be inferred to be ManagerDelegate. Note that you don't have to explicitly say:

typealias DelegateType = ManagerDelegate

In fact, you can totally remove = SomeManagerDelegate and just say:

associatedtype DelegateType

so it is far from the truth that the = is "the only thing keeping it together".

This = TypeName syntax doesn't seem very well documented. Here's a related Swift forums post.

Returning Two Types from one Function using Protocol Conformance

Suppose your method compiles,

Let's create another type that conforms to PriceCalculatable:

struct Foo : PriceCalculatable {}

Now we try to call your method:

let foo: Foo = PriceCalculator.culculateFinalPrice(for: someProducts, applying: myCoupon)

From the compiler's perspective, the above compiles, yet this results in an inconsistency at runtime. How can the runtime convert a Double (finalPrice) into a Foo?

Therefore, your method should not compile.

To make it work, you can create a ConvertileFromDouble protocol and make Int and Double conform to it. In the protocol, you need to specify an initialiser that takes a Double as argument.

protocol ConvertibleFromDouble {
init(_ doubleValue: Double)
}

extension Int: ConvertibleFromDouble {

}

extension Double: ConvertibleFromDouble {

}

class PriceCalculator {
static func culculateFinalPrice<T: ConvertibleFromDouble>(for products: [Product],
applying coupon: Coupon?) -> T {
...


Related Topics



Leave a reply



Submit