diff --git a/Sources/CasePaths/CasePathable.swift b/Sources/CasePaths/CasePathable.swift index eea3ad9f..bfad75f4 100644 --- a/Sources/CasePaths/CasePathable.swift +++ b/Sources/CasePaths/CasePathable.swift @@ -69,33 +69,20 @@ extension Case { self = Case()[keyPath: keyPath] } - // #if swift(>=6) - // public subscript( - // dynamicMember keyPath: KeyPath> - // & Sendable - // ) -> Case - // where Value: CasePathable { - // Case( - // embed: { embed(Value.allCasePaths[keyPath: keyPath].embed($0)) }, - // extract: { extract(from: $0).flatMap(Value.allCasePaths[keyPath: keyPath].extract) } - // ) - // } - // #else public subscript( dynamicMember keyPath: KeyPath> ) -> Case where Value: CasePathable { - @UncheckedSendable var keyPath = keyPath + let keyPath = keyPath.unsafeSendable() return Case( - embed: { [$keyPath] in - embed(Value.allCasePaths[keyPath: $keyPath.wrappedValue].embed($0)) + embed: { + embed(Value.allCasePaths[keyPath: keyPath].embed($0)) }, - extract: { [$keyPath] in - extract(from: $0).flatMap(Value.allCasePaths[keyPath: $keyPath.wrappedValue].extract) + extract: { + extract(from: $0).flatMap(Value.allCasePaths[keyPath: keyPath].extract) } ) } - // #endif public func embed(_ value: Value) -> Any { self._embed(value) @@ -521,25 +508,6 @@ extension AnyCasePath { } extension AnyCasePath where Value: CasePathable { - // #if swift(>=6) - // /// Returns a new case path created by appending the case path at the given key path to this one. - // /// - // /// This subscript is automatically invoked by case key path expressions via dynamic member - // /// lookup, and should not be invoked directly. - // /// - // /// - Parameter keyPath: A key path to a case-pathable case path. - // public subscript( - // dynamicMember keyPath: KeyPath> - // & Sendable - // ) -> AnyCasePath { - // AnyCasePath( - // embed: { self.embed(Value.allCasePaths[keyPath: keyPath].embed($0)) }, - // extract: { - // self.extract(from: $0).flatMap(Value.allCasePaths[keyPath: keyPath].extract(from:)) - // } - // ) - // } - // #else /// Returns a new case path created by appending the case path at the given key path to this one. /// /// This subscript is automatically invoked by case key path expressions via dynamic member @@ -549,17 +517,16 @@ extension AnyCasePath where Value: CasePathable { public subscript( dynamicMember keyPath: KeyPath> ) -> AnyCasePath { - @UncheckedSendable var keyPath = keyPath + let keyPath = keyPath.unsafeSendable() return AnyCasePath( - embed: { [$keyPath] in - embed(Value.allCasePaths[keyPath: $keyPath.wrappedValue].embed($0)) + embed: { + embed(Value.allCasePaths[keyPath: keyPath].embed($0)) }, - extract: { [$keyPath] in + extract: { extract(from: $0).flatMap( - Value.allCasePaths[keyPath: $keyPath.wrappedValue].extract(from:) + Value.allCasePaths[keyPath: keyPath].extract(from:) ) } ) } - // #endif } diff --git a/Sources/CasePaths/Internal/KeyPath+Sendable.swift b/Sources/CasePaths/Internal/KeyPath+Sendable.swift new file mode 100644 index 00000000..87bd30e2 --- /dev/null +++ b/Sources/CasePaths/Internal/KeyPath+Sendable.swift @@ -0,0 +1,22 @@ +#if compiler(>=6) + public typealias _SendableKeyPath = any Sendable & KeyPath +#else + public typealias _SendableKeyPath = KeyPath +#endif + +// NB: Dynamic member lookup does not currently support sendable key paths and even breaks +// autocomplete. +// +// * https://github.com/swiftlang/swift/issues/77035 +// * https://github.com/swiftlang/swift/issues/77105 +extension _AppendKeyPath { + @_transparent + package func unsafeSendable() -> _SendableKeyPath + where Self == KeyPath { + #if compiler(>=6) + unsafeBitCast(self, to: _SendableKeyPath.self) + #else + self + #endif + } +} diff --git a/Sources/CasePaths/Optional+CasePathable.swift b/Sources/CasePaths/Optional+CasePathable.swift index 235c42ff..2c3d9c61 100644 --- a/Sources/CasePaths/Optional+CasePathable.swift +++ b/Sources/CasePaths/Optional+CasePathable.swift @@ -54,19 +54,6 @@ extension Optional: CasePathable, CasePathIterable { } extension Case { - // #if swift(>=6) - // /// A case path to the presence of a nested value. - // /// - // /// This subscript can chain into an optional's wrapped value without explicitly specifying each - // /// `some` component. - // @_disfavoredOverload - // public subscript( - // dynamicMember keyPath: KeyPath> & Sendable - // ) -> Case - // where Value: CasePathable { - // self[dynamicMember: keyPath].some - // } - // #else /// A case path to the presence of a nested value. /// /// This subscript can chain into an optional's wrapped value without explicitly specifying each @@ -78,7 +65,6 @@ extension Case { where Value: CasePathable { self[dynamicMember: keyPath].some } - // #endif } extension Optional.AllCasePaths: Sequence { diff --git a/Tests/CasePathsTests/CaseSetTests.swift b/Tests/CasePathsTests/CaseSetTests.swift index 89a8efa2..24259722 100644 --- a/Tests/CasePathsTests/CaseSetTests.swift +++ b/Tests/CasePathsTests/CaseSetTests.swift @@ -16,14 +16,14 @@ } public subscript( - dynamicMember keyPath: CaseKeyPath & Sendable + dynamicMember keyPath: CaseKeyPath // & Sendable ) -> Member? { get { storage[keyPath].flatMap { $0[case: keyPath] } } set { storage[keyPath] = newValue.map(keyPath.callAsFunction) } } public subscript( - dynamicMember keyPath: CaseKeyPath & Sendable + dynamicMember keyPath: CaseKeyPath // & Sendable ) -> Bool { get { storage[keyPath].flatMap { $0[case: keyPath] } != nil } set { storage[keyPath] = newValue ? keyPath() : nil } @@ -153,9 +153,9 @@ extension CaseSet { @_disfavoredOverload public subscript( - dynamicMember keyPath: CaseKeyPath & Sendable + dynamicMember keyPath: CaseKeyPath // & Sendable ) -> CaseSetBuilder { - CaseSetBuilder(set: self, keyPath: keyPath) + CaseSetBuilder(set: self, keyPath: keyPath.unsafeSendable()) } }