Switching on Generic KeyPaths

I have an extension on CLLocationManager I’m using to store the default value for some of its methods rather than hard-coding them. It’s relatively simple:

import CoreLocation

extension CLLocationManager {
    
    static var defaultAllowsBackgroundLocationUpdates: Bool {
        return false
    }
    
    static var defaultDesiredAccuracy: CLLocationAccuracy {
        #if os(watchOS)
        return kCLLocationAccuracyHundredMeters
        #else
        return kCLLocationAccuracyBest
        #endif
    }
    
    static var defaultDistanceFilter: CLLocationDistance {
        return kCLDistanceFilterNone
    }
    
    static var defaultPausesLocationUpdatesAutomatically: Bool {
        return true
    }
    
    static var defaultShowsBackgroundLocationIndicator: Bool {
        return false
    }
    
}

One thing I do with this extension is registering some UserDefaults with the default values. I would like to write this code like this:

extension CLLocationManager {
    
    static func defaultValue<T>(
        forKeyPath keyPath: KeyPath<CLLocationManager, T>
    ) -> T? {
        switch keyPath {
        case \.allowsBackgroundLocationUpdates:
            return defaultAllowsBackgroundLocationUpdates
        case \.desiredAccuracy:
            return defaultDesiredAccuracy
        case \.distanceFilter:
            return defaultDistanceFilter
        case \.pausesLocationUpdatesAutomatically:
            return defaultPausesLocationUpdatesAutomatically
        case \.showsBackgroundLocationIndicator:
            return defaultShowsBackgroundLocationIndicator
        default:
            return nil
        }
    }
    
}

This doesn’t work, I’m assuming because the generics system can’t constrain each case statement to the type of the KeyPath that it’s matching.

All of the errors in case you’re interested.
/Users/jeff/Projects/Funhouse/Sleuth/Sleuth/CLLocationManagerDefaults.swift:45:14: error: type of expression is ambiguous without more context
        case \.allowsBackgroundLocationUpdates:
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/jeff/Projects/Funhouse/Sleuth/Sleuth/CLLocationManagerDefaults.swift:46:20: error: cannot convert return expression of type 'Bool' to return type 'T?'
            return defaultAllowsBackgroundLocationUpdates
                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                                          as! T
/Users/jeff/Projects/Funhouse/Sleuth/Sleuth/CLLocationManagerDefaults.swift:47:14: error: type of expression is ambiguous without more context
        case \.desiredAccuracy:
             ^~~~~~~~~~~~~~~~~
/Users/jeff/Projects/Funhouse/Sleuth/Sleuth/CLLocationManagerDefaults.swift:48:20: error: cannot convert return expression of type 'CLLocationAccuracy' (aka 'Double') to return type 'T?'
            return defaultDesiredAccuracy
                   ^~~~~~~~~~~~~~~~~~~~~~
                                          as! T
/Users/jeff/Projects/Funhouse/Sleuth/Sleuth/CLLocationManagerDefaults.swift:49:14: error: type of expression is ambiguous without more context
        case \.distanceFilter:
             ^~~~~~~~~~~~~~~~
/Users/jeff/Projects/Funhouse/Sleuth/Sleuth/CLLocationManagerDefaults.swift:50:20: error: cannot convert return expression of type 'CLLocationDistance' (aka 'Double') to return type 'T?'
            return defaultDistanceFilter
                   ^~~~~~~~~~~~~~~~~~~~~
                                         as! T
/Users/jeff/Projects/Funhouse/Sleuth/Sleuth/CLLocationManagerDefaults.swift:51:14: error: type of expression is ambiguous without more context
        case \.pausesLocationUpdatesAutomatically:
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/jeff/Projects/Funhouse/Sleuth/Sleuth/CLLocationManagerDefaults.swift:52:20: error: cannot convert return expression of type 'Bool' to return type 'T?'
            return defaultPausesLocationUpdatesAutomatically
                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                                             as! T
/Users/jeff/Projects/Funhouse/Sleuth/Sleuth/CLLocationManagerDefaults.swift:53:14: error: type of expression is ambiguous without more context
        case \.showsBackgroundLocationIndicator:
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/jeff/Projects/Funhouse/Sleuth/Sleuth/CLLocationManagerDefaults.swift:54:20: error: cannot convert return expression of type 'Bool' to return type 'T?'
            return defaultShowsBackgroundLocationIndicator
                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                                           as! T

Is there a way to write something like the second version, perhaps with dynamic dispatch? Is something like this ever likely to be supported with Swift’s generics system?

This works for me:

extension CLLocationManager {
    static func defaultValue<T>(
        forKeyPath keyPath: KeyPath<CLLocationManager, T>
    ) -> T? {
        switch keyPath {
        case \CLLocationManager.maximumRegionMonitoringDistance:
			return 0.2 as? T
        case \CLLocationManager.desiredAccuracy:
			return 0.1 as? T
        default:
            return nil
        }
    }
}

print(CLLocationManager.defaultValue(forKeyPath: \.desiredAccuracy)) // 0.1

Perfect, thanks! Here’s the fully-compiling version:

extension CLLocationManager {
    
    static func defaultValue<T>(
        forKeyPath keyPath: KeyPath<CLLocationManager, T>
    ) -> T? {
        switch keyPath {
        case \CLLocationManager.allowsBackgroundLocationUpdates:
            return defaultAllowsBackgroundLocationUpdates as? T
        case \CLLocationManager.desiredAccuracy:
            return defaultDesiredAccuracy as? T
        case \CLLocationManager.distanceFilter:
            return defaultDistanceFilter as? T
        case \CLLocationManager.pausesLocationUpdatesAutomatically:
            return defaultPausesLocationUpdatesAutomatically as? T
        case \CLLocationManager.showsBackgroundLocationIndicator:
            return defaultShowsBackgroundLocationIndicator as? T
        default:
            return nil
        }
    }
    
}