Skip to content

Commit

Permalink
Clean up dynamic member lookup key path sendability (#209)
Browse files Browse the repository at this point in the history
* Clean up dynamic member lookup key path sendability

Dynamic member lookup does not currently support sendable key paths, and
we've had commented-out code as a result, but let's clean it up in favor
of solutions employed in our other repos.

* Update KeyPath+Sendable.swift

* Update KeyPath+Sendable.swift

* fix

* fix
  • Loading branch information
stephencelis authored Dec 23, 2024
1 parent 1c26db4 commit f405870
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 61 deletions.
53 changes: 10 additions & 43 deletions Sources/CasePaths/CasePathable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,33 +69,20 @@ extension Case {
self = Case<Root>()[keyPath: keyPath]
}

// #if swift(>=6)
// public subscript<AppendedValue>(
// dynamicMember keyPath: KeyPath<Value.AllCasePaths, AnyCasePath<Value, AppendedValue>>
// & Sendable
// ) -> Case<AppendedValue>
// where Value: CasePathable {
// Case<AppendedValue>(
// embed: { embed(Value.allCasePaths[keyPath: keyPath].embed($0)) },
// extract: { extract(from: $0).flatMap(Value.allCasePaths[keyPath: keyPath].extract) }
// )
// }
// #else
public subscript<AppendedValue>(
dynamicMember keyPath: KeyPath<Value.AllCasePaths, AnyCasePath<Value, AppendedValue>>
) -> Case<AppendedValue>
where Value: CasePathable {
@UncheckedSendable var keyPath = keyPath
let keyPath = keyPath.unsafeSendable()
return Case<AppendedValue>(
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)
Expand Down Expand Up @@ -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<AppendedValue>(
// dynamicMember keyPath: KeyPath<Value.AllCasePaths, AnyCasePath<Value, AppendedValue>>
// & Sendable
// ) -> AnyCasePath<Root, AppendedValue> {
// AnyCasePath<Root, AppendedValue>(
// 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
Expand All @@ -549,17 +517,16 @@ extension AnyCasePath where Value: CasePathable {
public subscript<AppendedValue>(
dynamicMember keyPath: KeyPath<Value.AllCasePaths, AnyCasePath<Value, AppendedValue>>
) -> AnyCasePath<Root, AppendedValue> {
@UncheckedSendable var keyPath = keyPath
let keyPath = keyPath.unsafeSendable()
return AnyCasePath<Root, AppendedValue>(
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
}
22 changes: 22 additions & 0 deletions Sources/CasePaths/Internal/KeyPath+Sendable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#if compiler(>=6)
public typealias _SendableKeyPath<Root, Value> = any Sendable & KeyPath<Root, Value>
#else
public typealias _SendableKeyPath<Root, Value> = KeyPath<Root, Value>
#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<Root, Value>() -> _SendableKeyPath<Root, Value>
where Self == KeyPath<Root, Value> {
#if compiler(>=6)
unsafeBitCast(self, to: _SendableKeyPath<Root, Value>.self)
#else
self
#endif
}
}
14 changes: 0 additions & 14 deletions Sources/CasePaths/Optional+CasePathable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Member>(
// dynamicMember keyPath: KeyPath<Value.AllCasePaths, AnyCasePath<Value, Member?>> & Sendable
// ) -> Case<Member>
// 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
Expand All @@ -78,7 +65,6 @@ extension Case {
where Value: CasePathable {
self[dynamicMember: keyPath].some
}
// #endif
}

extension Optional.AllCasePaths: Sequence {
Expand Down
8 changes: 4 additions & 4 deletions Tests/CasePathsTests/CaseSetTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
}

public subscript<Member>(
dynamicMember keyPath: CaseKeyPath<Element, Member> & Sendable
dynamicMember keyPath: CaseKeyPath<Element, Member> // & Sendable
) -> Member? {
get { storage[keyPath].flatMap { $0[case: keyPath] } }
set { storage[keyPath] = newValue.map(keyPath.callAsFunction) }
}

public subscript(
dynamicMember keyPath: CaseKeyPath<Element, Void> & Sendable
dynamicMember keyPath: CaseKeyPath<Element, Void> // & Sendable
) -> Bool {
get { storage[keyPath].flatMap { $0[case: keyPath] } != nil }
set { storage[keyPath] = newValue ? keyPath() : nil }
Expand Down Expand Up @@ -153,9 +153,9 @@
extension CaseSet {
@_disfavoredOverload
public subscript<Member>(
dynamicMember keyPath: CaseKeyPath<Element, Member> & Sendable
dynamicMember keyPath: CaseKeyPath<Element, Member> // & Sendable
) -> CaseSetBuilder<Element, Member> {
CaseSetBuilder(set: self, keyPath: keyPath)
CaseSetBuilder(set: self, keyPath: keyPath.unsafeSendable())
}
}

Expand Down

0 comments on commit f405870

Please sign in to comment.