[Mistake] Member not found when accessing a value through two conditional subscripts

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 :slightly_smiling_face:

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

I just downloaded the RC 2, I was about to test in a few hours :face_with_open_eyes_and_hand_over_mouth: Thank you for your reply :smiling_face_with_tear:

Did you uncomment the last line?

Edit: I will uncomment it in my example, it's misleading.

It looks like you forgot : Point for Point2D.

Oh… and it works with this? I’ll have to elaborate my example more, because it doesn’t work for sure in my real implementation :relieved:

(I’m AFK, I’ll try your comments in a few hours)

Edit: I managed to try, and it indeed works :sweat: I’ll come back with a new example, hopefully not as big as the library (let’s hope it’s not caused by the complexity of the library :crossed_fingers:t3:).

After some investigation, I found you were right.

My "Point2D" (a lot more complex in the library) was conforming to Point, but as I was doing a breaking change, a few protocol requirements were missing and the type was not really conforming to the protocol. After I fixed all of the remaining issues, it compiled :smiling_face_with_tear:

Thank you for your reactivity, and sorry for the eager post :speak_no_evil:

1 Like