Home > front end >  Protocol with generics throws error when used as a property to call a method
Protocol with generics throws error when used as a property to call a method

Time:02-03

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.

CodePudding user response:

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)
    }
}
  • Related