New Dev
New Dev

Reputation: 49590

Inferring a generic type from its nested type

I am trying to create a Fetchable protocol that contains the location of where to get the objects from as part of its type, and instead of writing the fetch function with an explicit type parameter, like this:

func fetch<Model: Fetchable>(_ type: Model.Type, path: Model.Path) -> AnyPublisher<[Model], Error> {
   print(path.value)
   // ...
}

I would like Model to be inferred from the Model.Path parameter:

func fetch<Model: Fetchable>(path: Model.Path) -> AnyPublisher<[Model], Error> {
   print(path.value)
   // ...
}

This is inspired by @RobNapier's approach here. It's not exactly the same, and so I might be missing salient details to make it work.

Here's what I have:

protocol Locatable {
   associatedtype Model
   var value: String { get }
}

protocol Fetchable: Codable {
   associatedtype Path: Locatable where Path.Model == Self
}

struct Message {
   let content: String
}

extension Message: Fetchable, Codable {
   enum Path: Locatable {
      typealias Model = Message
      case forUser(_ userId: String)
      var value: String {
         switch self {
         case .forUser(let userId): return "/user/\(userId)/messages"
         }
      }
   }
}

When I call fetch, I get an error "Generic parameter 'Model' could not be inferred"

let pub = fetch(path: Message.Path.forUser("123"))

But this works with a fetch that accepts the type parameter explicitly (even infers its own Message.Path type):

let pub = fetch(Message.self, .forUser("123"))

Any idea how (if possible) to solve this?

Upvotes: 1

Views: 130

Answers (1)

Asperi
Asperi

Reputation: 257869

It is not enough information to infer, but if we write

let pub: AnyPublisher<[Message], Error> = fetch(path: Message.Path.forUser("123"))

everything goes well.

Update: nested type is just a type it is not dividable, so to help swift to infer parent we need to reverse declaration, like below (tested with Xcode 12.1):

func fetch<Path: Locatable>(path: Path) -> 
        AnyPublisher<[Path.Model], Error> where Path.Model: Fetchable {

and now your desired expression becomes possible

let pub = fetch(path: Message.Path.forUser("123"))

Upvotes: 2

Related Questions