TLDR
Original Post
Context: I'm writing a generic geographical library. I already introduced it in Wrong “Redundant conformance constraint” warning when I encountered a bogus with the generics system.
I'm completely rewriting the library to support multiple Coordinate Reference Systems (CRS), in a type-safe manner. The code is still a huge mess so I haven't pushed it, but I can if needed.
Anyway, I just encountered a new error: I can't access a value through two conditional subscripts. Maybe I missed something, but I think it's a compiler issue. I wrote a MRE, so you can understand what I mean by that:
// MARK: - CRS
protocol CoordinateReferenceSystem {
associatedtype CoordinateComponents
}
protocol TwoDimensionsCRS: CoordinateReferenceSystem where CoordinateComponents == (X, Y) {
associatedtype X
associatedtype Y
}
/// Angle-based coordinates
protocol GeographicCRS: CoordinateReferenceSystem {}
/// Cartesian coordinates
protocol GeocentricCRS: CoordinateReferenceSystem {}
enum WGS84Geographic2DCRS: TwoDimensionsCRS, GeographicCRS {
typealias X = Double
typealias Y = Double
}
// MARK: - Coordinates
@dynamicMemberLookup
protocol Coordinates<CRS> {
associatedtype CRS: CoordinateReferenceSystem
typealias Components = CRS.CoordinateComponents
var components: Components { get }
subscript<T>(dynamicMember keyPath: KeyPath<Components, T>) -> T { get }
}
extension Coordinates {
subscript<T>(dynamicMember keyPath: KeyPath<Components, T>) -> T {
self.components[keyPath: keyPath]
}
}
protocol TwoDimensionsCoordinates: Coordinates where CRS: TwoDimensionsCRS {
var x: CRS.X { get }
var y: CRS.Y { get }
}
extension TwoDimensionsCoordinates {
var x: CRS.X { self.components.0 }
var y: CRS.Y { self.components.1 }
}
extension TwoDimensionsCoordinates where CRS: GeographicCRS {
var latitude: CRS.X { self.x }
var longitude: CRS.Y { self.y }
}
struct Coordinates2D: TwoDimensionsCoordinates {
typealias CRS = WGS84Geographic2DCRS
var components: Components
}
// MARK: - Points
@dynamicMemberLookup
protocol Point<C, Metadata> {
associatedtype C: Coordinates
associatedtype Metadata
var coordinates: C { get }
var metadata: Metadata { get }
subscript<T>(dynamicMember keyPath: KeyPath<C, T>) -> T { get }
}
extension Point {
subscript<T>(dynamicMember keyPath: KeyPath<C, T>) -> T {
self.coordinates[keyPath: keyPath]
}
}
// Doesn't change anything
//extension Point where C.CRS: GeographicCRS {
// subscript<T>(dynamicMember keyPath: KeyPath<C, T>) -> T {
// self.coordinates[keyPath: keyPath]
// }
//}
struct Point2D {
typealias Metadata = Void
var coordinates: Coordinates2D
var metadata: Metadata
}
// MARK: - Code playground
let point = Point2D(coordinates: .init(components: (2, 3)), metadata: ())
let l1 = point.coordinates.latitude
// error: value of type 'Point2D' has no member 'latitude'
let l2 = point.latitude
Since there is a dynamic member lookup from Point
to its Coordinates
and point.coordinates.latitude
gives the correct result, I would expect point.latitude
to compile, but it doesn't.
Am I missing something? I tried to add
extension Point where C.CRS: GeographicCRS {
subscript<T>(dynamicMember keyPath: KeyPath<C, T>) -> T {
self.coordinates[keyPath: keyPath]
}
}
but it doesn't help the compiler find the member.
NOTE: I couldn't make this example tinier, but if you have ideas, please let me know
NOTE 2: For easier copy-paste, I created Swift – Member not found when accessing a value through two conditional subscripts · GitHub.
Environment
- Xcode: Version 14.0.1 (14A400)
- Toolchain: Xcode 14.0.1