Search code examples
swiftprotocolstype-inference

Type inference for associated type doesn't work in child protocol


Consider the following two protocols:

protocol Parser {
    associatedtype ResultType

    func parse(_ data: Data) throws -> ResultType
}

protocol JSONParser: Parser {
    func parse(_ json: [AnyHashable:Any]) throws -> ResultType
}

extension JSONParser {
    func parse(_ data: Data) throws -> ResultType {
        return try parse([:])
    }
}

Basically we have a base "raw" parser that parse data, and an "enhanced" child protocol that decodes the data into a dictionary and allows conforming types to access the decoded JSON.

Now, the problem is with the type witness inference that no longer works for types adopting the child protocol.

This works:

class RawNumberParser: Parser {
    func parse(_ data: Data) throws -> Int {
        return data.count
    }
}

, as the compiler automatically infer the type Int for the ResultType associated type.

However, the following code doesn't compile:

class AdvancedNumberParser: JSONParser {
    func parse(_ json: [AnyHashable : Any]) throws -> Int {
        return json.count
    }
}

error: type 'AdvancedNumberParser' does not conform to protocol 'JSONParser'

Explicitly declaring the type alias makes the error go away:

class AdvancedNumberParser: JSONParser {
    typealias ResultType = Int

    func parse(_ json: [AnyHashable : Any]) throws -> Int {
        return json.count
    }
}

Am I missing some declarations, or is the compiler not capable to infer the associated type for the child protocol?


Solution

  • Found a workaround: adding an associated type in the child protocol:

    protocol JSONParser: Parser {
        associatedtype JSONResultType = ResultType
    
        func parse(_ json: [AnyHashable:Any]) throws -> JSONResultType
    }
    
    extension JSONParser {
        func parse(_ data: Data) throws -> JSONResultType {
            return try parse([:])
        }
    }
    

    For some reason it seems the type witness inference is shallow, it maps only for the current level.