Search code examples
swiftgenericsdecodable

Generic Function: Type of expression is ambiguous without more context


I have a static function in a class that takes a generic type that must conform to decodable, however when I call this function I get the following error: "Type of expression is ambiguous without more context". The Tour class (which is the type I'm passing to the function) conforms to Decodable and inherits from the CoreDataModel class.

This is occurring on the new Xcode 12.0 Beta in DashboardNetworkAdapter when I call CoreDataModel.create (line 7 on the snippet I've shared for that class).

Edit minimal reproducible example:

DashboardNetworkAdapter:

class DashboardNetworkAdapter {
    
    // MARK: - GET
    
    public func syncTours(page: Int, completion: @escaping(Bool, Error, Bool) -> Void) {
        
        if let path = Bundle.main.path(forResource: "Tours", ofType: "json") {
            do {
                let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
                CoreDataModel.create(Array<Tour>.self, from: data) { success, error in
                    completion(success, error, false)
                }
            } catch let error {
                completion(false, error, false)
            }
        }
        
    }
    
}

CoreDataModel:

public class CoreDataModel: NSManagedObject {
    
    // MARK: - Variables
    
    @NSManaged public var id: Int64
    @NSManaged public var createdAt: Date?
    @NSManaged public var updatedAt: Date?


    
    // MARK: - CRUD
    
    static func create<T>(_ type: T.Type, from data: Data, completion: @escaping (Bool, Error?) -> Void) where T : Decodable {
        DataCoordinator.performBackgroundTask { context in
            do {
                let _ = try DataDecoder(context: context).decode(type, from: data, completion: {
                    completion(true, nil)
                })
            } catch let error {
                completion(false, error)
            }
        }
    }
}

Tour:

@objc(Tour)
class Tour: CoreDataModel, Decodable {
    
    // MARK: - Variables
    
    @NSManaged public var name: String?
    @NSManaged public var image: URL?
    @NSManaged public var owned: Bool
    @NSManaged public var price: Double
    
    
    
    // MARK: - Coding Keys
    
    enum CodingKeys: String, CodingKey {
        case id = "id"
        case name = "name"
        case image = "image"
        case owned = "owned"
        case price = "price"
        case updatedAt = "updated_at"
        case createdAt = "created_at"
    }
    
    
    
    // MARK: - Initializer
    
    required convenience init(from decoder: Decoder) throws {
        guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError("Cannot find decoding context")}
        self.init(context: context)
        
        let topLevel = try decoder.container(keyedBy: PaginatedResponseCodingKeys.self)
        let values = try topLevel.nestedContainer(keyedBy: CodingKeys.self, forKey: .data)
        let id = try values.decode(Int64.self, forKey: .id)
        let name = try values.decode(String.self, forKey: .name)
        let image = try values.decode(String.self, forKey: .image)
        let owned = try values.decode(Int.self, forKey: .owned)
        let price = try values.decode(Double.self, forKey: .price)
        let updatedAt = try values.decode(Date.self, forKey: .updatedAt)
        let createdAt = try values.decode(Date.self, forKey: .createdAt)
        
        self.id = id
        self.name = name
        self.image = URL(string: image)
        self.owned = owned == 1
        self.price = price
        self.updatedAt = updatedAt
        self.createdAt = createdAt
    }
    
    
    init(context: NSManagedObjectContext) {
        guard let entity = NSEntityDescription.entity(forEntityName: "Tour", in: context) else { fatalError() }
        super.init(entity: entity, insertInto: context)
    }
    
}

DataDecoder:

class DataDecoder: JSONDecoder {
    
    // MARK: - Variables
    
    var context: NSManagedObjectContext!
    private var persistent: Bool = true
    
    
    
    
    // MARK: - Initializers
    
    public init(persistent: Bool = true, context: NSManagedObjectContext) {
        super.init()
        
        self.dateDecodingStrategy = .custom({ (decoder) -> Date in
            let container = try decoder.singleValueContainer()
            let string = try container.decode(String.self)

            let dateFormatter = DateFormatter()
            locale = .autoupdatingCurrent
            dateFormat = "yyyy-MM-dd HH:mm:ss"

            return dateFormatter.date(from: string)!
        })
        
        self.context = context
        userInfo[.context] = context
    }
    
    
    
    
    // MARK: - Utilities
    
        public func decode<T>(_ type: T.Type, from data: Data, completion: (() -> Void)? = nil) throws -> T where T : Decodable {
            let result = try super.decode(type, from: data)
            
            if(persistent) {
                saveContext(completion: completion)
            }
            return result
        }
        
        
        private func saveContext(completion: (() -> Void)? = nil)  {
            guard let context = context else { fatalError("Cannot Find Decoding Context") }
            context.performAndWait {
                try? context.save()
                completion?()
                context.reset()
            }
        }
        
    }

CodingUserInfoKey Extension:

extension CodingUserInfoKey {
    
    static let context = CodingUserInfoKey(rawValue: "context")!
    
}

Solution

  • There's a type mismatch between what completion closure expects for its Error parameter, and what it gets from the inferred type of the CoreDataModel.create completion closure, which is Error?:

    public func syncTours(page: Int, completion: @escaping(Bool, Error, Bool) -> Void) {
       ...
    
            CoreDataModel.create(Array<Tour>.self, from: data) { success, error in
                // error is of type Error?, but completion expects Error
                completion(success, error, false)
            }
       ...
    }
    

    In general, whenever you face type inference issues or errors, make each type explicit, and you'll see exactly exactly where the mismatch is. For example, below you could be explicit about the inner closure signature:

    CoreDataModel.create([Tour].self, from: data) { (success: Bool, err: Error?) -> Void in
    
    }