Protocol with generics throws error when used as a property to call a method

377 views Asked by At

I have a protocol SomeObjectFactory whose method createSomeObjectWithConfiguration(_ config: SomeObjectConfiguration<T>) is used inside the class Builder. When I tried to compile this code with Swift 5.7 I ran into an error

Member 'configWithExperience' cannot be used on value of type 'any configurationFactory'; consider using a generic constraint instead

Here is the implementation below

import Combine
import Foundation

final class SomeObject<T: Combine.Scheduler> {}

struct Experience {
    let id: String
}

struct SomeObjectConfiguration<T: Combine.Scheduler> {
    let scheduler: T
}

protocol SomeObjectFactory {
    associatedtype T: Combine.Scheduler
    func createSomeObjectWithConfiguration(_ config: SomeObjectConfiguration<T>) -> SomeObject<T>
}

protocol ConfigurationFactory {
    associatedtype T: Combine.Scheduler
    func configWithExperience(_ experience: Experience) -> SomeObjectConfiguration<T>
}

final class Builder<T: Combine.Scheduler> {
    
    private let configurationFactory: any ConfigurationFactory
    
    init(configurationFactory: any ConfigurationFactory) {
        self.configurationFactory = configurationFactory
    }
    
    func createSomeObject(_ experience: Experience) {
        let someObjectConfiguration: SomeObjectConfiguration<T> = configurationFactory.configWithExperience(experience)
    }
}

I was hoping to create a someObjectConfiguration from the configurationFactory instance of the Builder.

1

There are 1 answers

1
Alexander On BEST ANSWER

any ConfigurationFactory really does mean, any ConfigurationFactory. There's no guarantee the ConfigurationFactory.T of that factory will be the same as the Builder.T that contains it. You need to add a constraint that those two should match.

One way to do that is with a primary associated type, like so:

// Make `T` a primary associated type
// https://github.com/apple/swift-evolution/blob/main/proposals/0346-light-weight-same-type-syntax.md

+protocol ConfigurationFactory<T> {
-protocol ConfigurationFactory {
    associatedtype T: Combine.Scheduler
    func configWithExperience(_ experience: Experience) -> SomeObjectConfiguration<T>
}

final class Builder<T: Combine.Scheduler> {

+    // Constant the `T` of the factory to be the `T` of this builder   
+    private let configurationFactory: any ConfigurationFactory<T>
-    private let configurationFactory: any ConfigurationFactory
    
     // Likewise, update the initializer to match
+    init(configurationFactory: any ConfigurationFactory<T>) {
-    init(configurationFactory: any ConfigurationFactory) {
        self.configurationFactory = configurationFactory
    }
    
    func createSomeObject(_ experience: Experience) {
        let someObjectConfiguration: SomeObjectConfiguration<T> = configurationFactory.configWithExperience(experience)
    }
}