1

I am trying to map enum values to properties on a struct, so the properties can be accessed by enum, and the properties can be iterated and processed in a generic way. Here is a contrived example, which demonstrates the compiler errors I'm getting:

import UIKit

protocol AProtocol: Equatable {
    var someInstanceProperty: String { get }
}

extension Bool: AProtocol {
    var someInstanceProperty: String { "Bool" }
}
extension Int: AProtocol {
    var someInstanceProperty: String { "Int" }
}
extension String: AProtocol {
    var someInstanceProperty: String { "String" }
}

struct Values {
    var someBool = false
    var someInt = 0
    var someString = "hello"
    
    enum Properties {
        case someB, someI, someS
    }
    
    let map1: [Properties: PartialKeyPath<Values>] = [
        .someB: \Values.someBool,
        .someI: \Values.someInt,
        .someS: \Values.someString,
    ]
    
    let map2: [Properties: KeyPath<Values, AProtocol>] = [
        .someB: \Values.someBool, // Cannot convert value of type 'KeyPath<Values, Bool>' to expected dictionary value type 'KeyPath<Values, AProtocol>'
        .someI: \Values.someInt, // Cannot convert value of type 'KeyPath<Values, Int>' to expected dictionary value type 'KeyPath<Values, AProtocol>'
        .someS: \Values.someString, // Cannot convert value of type 'KeyPath<Values, String>' to expected dictionary value type 'KeyPath<Values, AProtocol>'
    ]
    
    func performSomeAction<T: AProtocol>(key: Properties, keyPath: KeyPath<Values, T>) {
        print("key \(key), value \(self[keyPath: keyPath].someInstanceProperty)")
    }
    
    func getAll1() {
        for (key, keyPath) in map1 {
            performSomeAction(key: key, keyPath: keyPath) // Cannot convert value of type 'PartialKeyPath<Values>' to expected argument type 'KeyPath<Values, T>'
        }
    }
    
    func getAll2() {
        for (key, keyPath) in map1 {
            print("key \(key), value \(self[keyPath: keyPath].someInstanceProperty)") // Value of type 'Any?' has no member 'someInstanceProperty'
        }
    }
    
    func getAll3() {
        for (key, keyPath) in map1 {
            print("key \(key), value \((self[keyPath: keyPath] as! AProtocol).someInstanceProperty)") // Protocol 'AProtocol' can only be used as a generic constraint because it has Self or associated type requirements
        }
    }
}

In map1, the value type is PartialKeyPath. I can't use KeyPath because the value types are different (even though they all conform to AProtocol), so I get a compiler error (see map2). The problem with map1 is that the values lose their type information. The three getAll functions demonstrate various approaches I've tried to overcome this, but haven't been successful. The closest I've got is getAll3, which works if I remove Equatable conformance from AProtocol. Unfortunately I need the values to be equatable – in my real code I'm comparing previously stored values with current property values. Is this possible?

Nick
  • 3,958
  • 4
  • 32
  • 47
  • If you just remove the `: Equatable` inheritance of your protocol, you won't get the "AProtocol can only be used as a generic constraint because it has Self or associated type requirements" error in `getAll3`. Is that something the protocol in your real code has to have? – Sweeper Aug 26 '22 at 08:32
  • Yes, not required in the example here of course, but I do require it in my real project. I'm comparing previously stored values with the current property values to determine which need updating. – Nick Aug 26 '22 at 08:51
  • Well, then you should probably redesign the whole thing (which I can't help you with without knowing more about what you are doing exactly). Equatable things of different types should not go into the same collection. You *can* technically work around it by making your own `AnyEquatable` [like this](https://stackoverflow.com/q/68878549/5133585), but I don't think this is the best way to go. – Sweeper Aug 26 '22 at 09:01

0 Answers0