Method overloading with generics

Hello, I have a question regarding generics overloading.
I faced a problem in my project. I tried to handle Data differently and it worked perfectly until I wrapped it in another generic layer.
My understanding of generics is that generic type is a concrete type.
In the example below I expected to have all the prints to be equal to the first one.
So, is it expected behaviour or a bug?

import Foundation

@available(iOS 8, *)
public protocol APICall {
    var url: URL { get }
    func body(using jsonEncoder: JSONEncoder) throws -> Data?
}

public extension APICall {
    func body(using jsonEncoder: JSONEncoder) throws -> Data? { nil }
}

@available(iOS 8, *)
public extension URLRequest {
    
    init<Endpoint: APICall>(endpoint: Endpoint) {
        self.init(url: endpoint.url)
    }

    init<Endpoint: APICall>(endpoint: Endpoint, bodyEncoder: JSONEncoder) throws {
        self.init(endpoint: endpoint)
        self.httpBody = try endpoint.body(using: bodyEncoder)
    }

    init(endpoint: EmbodiedEndpoint<Data>, bodyEncoder: JSONEncoder) throws {
        self.init(endpoint: endpoint)
        self.httpBody = endpoint.body
    }

}

@available(iOS 8, *)
public struct Endpoint: APICall {
    public let url: URL
}

@available(iOS 8, *)
public struct EmbodiedEndpoint<Body: Encodable> {
    
    public let url: URL
    fileprivate let body: Body
    
    public init(url: URL, body: Body) {
        self.url = url
        self.body = body
    }
    
}

extension EmbodiedEndpoint: APICall {
    
    public func body(using jsonEncoder: JSONEncoder) throws -> Data? {
        try jsonEncoder.encode(body)
    }
    
}

public extension EmbodiedEndpoint<Data> {
    func body(using jsonEncoder: JSONEncoder) throws -> Data? { body }
}

struct Request: Codable {
    let id: Int
}

struct NetworkService {
    
    let jsonEncoder: JSONEncoder
    
    func call<Endpoint: APICall>(endpoint: Endpoint) throws {
        print("Running endpoint of type: \(type(of: endpoint))")
        let urlRequest = try URLRequest(endpoint: endpoint, bodyEncoder: jsonEncoder)
        print(String(decoding: urlRequest.httpBody!, as: UTF8.self))
    }
    
}

let encoder = JSONEncoder()

let url = URL(string: "https://www.google.com")!
let request = Request(id: 1)

func testEntity() throws {
    let endpoint = EmbodiedEndpoint(url: url, body: request)
    let urlRequest = try URLRequest(endpoint: endpoint, bodyEncoder: encoder)
    print(String(decoding: urlRequest.httpBody!, as: UTF8.self))
}

func testData() throws {
    let data = try encoder.encode(request)
    let endpoint = EmbodiedEndpoint(url: url, body: data)
    let urlRequest = try URLRequest(endpoint: endpoint, bodyEncoder: encoder)
    print(String(decoding: urlRequest.httpBody!, as: UTF8.self))
}

func testGenericWrapper() throws {
    let networkService = NetworkService(jsonEncoder: encoder)

    let data = try encoder.encode(request)
    let endpoint = EmbodiedEndpoint(url: url, body: data)
    try networkService.call(endpoint: endpoint)
}

try testEntity() // prints {"id":1}
try testData() // prints {"id":1}
try testGenericWrapper() // prints "eyJpZCI6MX0="

This is expected behavior; each protocol requirement has a single implementation in the protocol conformance. In your case that’s the one in the unconstrained extension.

1 Like

Why is it working for the testData function then?

Because testData() doesn’t call through the protocol requirement; it directly refers to the overload in the constrained extension.

2 Likes