Skip to content

Commit

Permalink
Require sendable IDs in cancellation/reducers (#3318)
Browse files Browse the repository at this point in the history
* Require sendable IDs in cancellation/reducers

Many of our APIs warn that we are passing hashable identifiers across
concurrency boundaries, so we should require that they're sendable.

* fix

* fix

* Fix
  • Loading branch information
stephencelis authored Aug 29, 2024
1 parent a7e2e73 commit b8277a0
Show file tree
Hide file tree
Showing 8 changed files with 29 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Sources/ComposableArchitecture/Effects/Cancellation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ extension Effect {
/// - cancelInFlight: Determines if any in-flight effect with the same identifier should be
/// canceled before starting this new one.
/// - Returns: A new effect that is capable of being canceled by an identifier.
public func cancellable<ID: Hashable>(id: ID, cancelInFlight: Bool = false) -> Self {
public func cancellable(id: some Hashable & Sendable, cancelInFlight: Bool = false) -> Self {
@Dependency(\.navigationIDPath) var navigationIDPath

switch self.operation {
Expand Down
4 changes: 2 additions & 2 deletions Sources/ComposableArchitecture/Effects/Debounce.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ extension Effect {
/// - scheduler: The scheduler you want to deliver the debounced output to.
/// - options: Scheduler options that customize the effect's delivery of elements.
/// - Returns: An effect that publishes events only after a specified time elapses.
public func debounce<ID: Hashable, S: Scheduler>(
id: ID,
public func debounce<S: Scheduler>(
id: some Hashable & Sendable,
for dueTime: S.SchedulerTimeType.Stride,
scheduler: S,
options: S.SchedulerOptions? = nil
Expand Down
4 changes: 2 additions & 2 deletions Sources/ComposableArchitecture/Effects/Throttle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ extension Effect {
/// `false`, the publisher emits the first element received during the interval.
/// - Returns: An effect that emits either the most-recent or first element received during the
/// specified interval.
public func throttle<ID: Hashable, S: Scheduler>(
id: ID,
public func throttle<S: Scheduler>(
id: some Hashable & Sendable,
for interval: S.SchedulerTimeType.Stride,
scheduler: S,
latest: Bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
}
}

public struct _StoreCollection<ID: Hashable, State, Action>: RandomAccessCollection {
public struct _StoreCollection<ID: Hashable & Sendable, State, Action>: RandomAccessCollection {
private let store: Store<IdentifiedArray<ID, State>, IdentifiedAction<ID, Action>>
private let data: IdentifiedArray<ID, State>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import OrderedCollections
///
/// Use this type for modeling a feature's domain that needs to present child features using
/// ``Reducer/forEach(_:action:element:fileID:filePath:line:column:)-3dw7i``.
public enum IdentifiedAction<ID: Hashable, Action>: CasePathable {
public enum IdentifiedAction<ID: Hashable & Sendable, Action>: CasePathable {
/// An action sent to the element at a given identifier.
case element(id: ID, action: Action)

Expand Down Expand Up @@ -163,7 +163,10 @@ extension Reducer {
@inlinable
@warn_unqualified_access
public func forEach<
ElementState, ElementAction, ID: Hashable, Element: Reducer<ElementState, ElementAction>
ElementState,
ElementAction,
ID: Hashable & Sendable,
Element: Reducer<ElementState, ElementAction>
>(
_ toElementsState: WritableKeyPath<State, IdentifiedArray<ID, ElementState>>,
action toElementAction: AnyCasePath<Action, (ID, ElementAction)>,
Expand All @@ -190,7 +193,7 @@ extension Reducer {
}

public struct _ForEachReducer<
Parent: Reducer, ID: Hashable, Element: Reducer
Parent: Reducer, ID: Hashable & Sendable, Element: Reducer
>: Reducer {
@usableFromInline
let parent: Parent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -710,15 +710,15 @@ public struct _PresentationReducer<Base: Reducer, Destination: Reducer>: Reducer
}

@usableFromInline
struct PresentationDismissID: Hashable {
struct PresentationDismissID: Hashable, Sendable {
@usableFromInline init() {}
}
@usableFromInline
struct OnFirstAppearID: Hashable {
struct OnFirstAppearID: Hashable, Sendable {
@usableFromInline init() {}
}

public struct _PresentedID: Hashable {
public struct _PresentedID: Hashable, Sendable {
@inlinable
public init() {
self.init(internal: ())
Expand All @@ -729,8 +729,8 @@ public struct _PresentedID: Hashable {
}

extension Task<Never, Never> {
internal static func _cancel<ID: Hashable>(
id: ID,
internal static func _cancel(
id: some Hashable,
navigationID: NavigationIDPath
) {
withDependencies {
Expand All @@ -742,8 +742,8 @@ extension Task<Never, Never> {
}

extension Effect {
internal func _cancellable<ID: Hashable>(
id: ID = _PresentedID(),
internal func _cancellable(
id: some Hashable & Sendable = _PresentedID(),
navigationIDPath: NavigationIDPath,
cancelInFlight: Bool = false
) -> Self {
Expand All @@ -753,8 +753,8 @@ extension Effect {
self.cancellable(id: id, cancelInFlight: cancelInFlight)
}
}
internal static func _cancel<ID: Hashable>(
id: ID = _PresentedID(),
internal static func _cancel(
id: some Hashable = _PresentedID(),
navigationID: NavigationIDPath
) -> Self {
withDependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,10 @@ extension StackElementID: ExpressibleByIntegerLiteral {
}
}

private struct NavigationDismissID: Hashable {
let elementID: AnyHashable
private struct NavigationDismissID: Hashable, Sendable {
private let elementID: AnyHashableSendable

init(elementID: some Hashable & Sendable) {
self.elementID = AnyHashableSendable(elementID)
}
}
4 changes: 2 additions & 2 deletions Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ import SwiftUI
"Pass 'ForEach' a store scoped to an identified array and identified action, instead. For more information, see the following article: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7#Replacing-ForEachStore-with-ForEach]"
)
public struct ForEachStore<
EachState, EachAction, Data: Collection, ID: Hashable, Content: View
EachState, EachAction, Data: Collection, ID: Hashable & Sendable, Content: View
>: View {
public let data: Data
let content: Content
Expand Down Expand Up @@ -232,7 +232,7 @@ public struct ForEachStore<
#endif

extension Case {
fileprivate subscript<ID: Hashable, Action>(id id: ID) -> Case<Action>
fileprivate subscript<ID: Hashable & Sendable, Action>(id id: ID) -> Case<Action>
where Value == (id: ID, action: Action) {
Case<Action>(
embed: { (id: id, action: $0) },
Expand Down

0 comments on commit b8277a0

Please sign in to comment.