-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Address @Shared sendability. #3329
Changes from 2 commits
70cf0bd
68023fa
17eed0c
4b38430
2dac7a3
d277743
53d5389
414e4fa
0020365
36d2323
2faa246
85354f4
e77c431
93e858d
833be15
d185c0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
struct AnySendable: @unchecked Sendable { | ||
let base: Any | ||
@inlinable | ||
init<Base: Sendable>(_ base: Base) { | ||
self.base = base | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,9 +7,9 @@ | |
/// | ||
/// See the article <doc:SharingState> for more information, in particular the | ||
/// <doc:SharingState#Custom-persistence> section. | ||
public protocol PersistenceReaderKey<Value> { | ||
public protocol PersistenceReaderKey<Value>: Sendable { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Persistence keys need to be sendable because they are captured in some |
||
/// A type that can be loaded or subscribed to in an external system. | ||
associatedtype Value | ||
associatedtype Value: Sendable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
/// A type representing the hashable identity of a persistence key. | ||
associatedtype ID: Hashable = Self | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -154,7 +154,7 @@ extension PersistenceReaderKey { | |
/// A type defining a user defaults persistence strategy. | ||
/// | ||
/// See ``PersistenceReaderKey/appStorage(_:)-4l5b`` to create values of this type. | ||
public struct AppStorageKey<Value> { | ||
public struct AppStorageKey<Value: Sendable>: Sendable { | ||
private let lookup: any Lookup<Value> | ||
private let key: String | ||
private let store: UserDefaults | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically |
||
|
@@ -362,13 +362,13 @@ private enum SharedAppStorageLocals { | |
@TaskLocal static var isSetting = false | ||
} | ||
|
||
private protocol Lookup<Value> { | ||
associatedtype Value | ||
private protocol Lookup<Value>: Sendable { | ||
associatedtype Value: Sendable | ||
func loadValue(from store: UserDefaults, at key: String, default defaultValue: Value?) -> Value? | ||
func saveValue(_ newValue: Value, to store: UserDefaults, at key: String) | ||
} | ||
|
||
private struct CastableLookup<Value>: Lookup { | ||
private struct CastableLookup<Value: Sendable>: Lookup, Sendable { | ||
func loadValue( | ||
from store: UserDefaults, | ||
at key: String, | ||
|
@@ -421,7 +421,7 @@ private struct URLLookup: Lookup { | |
} | ||
} | ||
|
||
private struct RawRepresentableLookup<Value: RawRepresentable, Base: Lookup>: Lookup | ||
private struct RawRepresentableLookup<Value: RawRepresentable & Sendable, Base: Lookup>: Lookup, Sendable | ||
where Value.RawValue == Base.Value { | ||
let base: Base | ||
func loadValue( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,7 @@ extension Shared { | |
/// - persistenceKey: A persistence key associated with the shared reference. It is responsible | ||
/// for loading and saving the shared reference's value from some external source. | ||
public init( | ||
wrappedValue value: @autoclosure @escaping () -> Value, | ||
wrappedValue value: @autoclosure @escaping @Sendable () -> Value, | ||
_ persistenceKey: some PersistenceKey<Value>, | ||
fileID: StaticString = #fileID, | ||
line: UInt = #line | ||
|
@@ -93,7 +93,7 @@ extension Shared { | |
} | ||
|
||
private init( | ||
throwingValue value: @autoclosure @escaping () throws -> Value, | ||
throwingValue value: @autoclosure @escaping @Sendable () throws -> Value, | ||
_ persistenceKey: some PersistenceKey<Value>, | ||
fileID: StaticString = #fileID, | ||
line: UInt = #line | ||
|
@@ -154,7 +154,7 @@ extension Shared { | |
/// - persistenceKey: A persistence key associated with the shared reference. It is responsible | ||
/// for loading and saving the shared reference's value from some external source. | ||
public init<Key: PersistenceKey<Value>>( | ||
wrappedValue value: @autoclosure @escaping () -> Value, | ||
wrappedValue value: @autoclosure @escaping @Sendable () -> Value, | ||
_ persistenceKey: PersistenceKeyDefault<Key>, | ||
fileID: StaticString = #fileID, | ||
line: UInt = #line | ||
|
@@ -177,7 +177,7 @@ extension SharedReader { | |
/// - persistenceKey: A persistence key associated with the shared reference. It is responsible | ||
/// for loading the shared reference's value from some external source. | ||
public init( | ||
wrappedValue value: @autoclosure @escaping () -> Value, | ||
wrappedValue value: @autoclosure @escaping @Sendable () -> Value, | ||
_ persistenceKey: some PersistenceReaderKey<Value>, | ||
fileID: StaticString = #fileID, | ||
line: UInt = #line | ||
|
@@ -252,7 +252,7 @@ extension SharedReader { | |
} | ||
|
||
private init( | ||
throwingValue value: @autoclosure @escaping () throws -> Value, | ||
throwingValue value: @autoclosure @escaping @Sendable () throws -> Value, | ||
_ persistenceKey: some PersistenceReaderKey<Value>, | ||
fileID: StaticString = #fileID, | ||
line: UInt = #line | ||
|
@@ -312,7 +312,7 @@ extension SharedReader { | |
/// - persistenceKey: A persistence key associated with the shared reference. It is responsible | ||
/// for loading the shared reference's value from some external source. | ||
public init<Key: PersistenceReaderKey<Value>>( | ||
wrappedValue value: @autoclosure @escaping () -> Value, | ||
wrappedValue value: @autoclosure @escaping @Sendable () -> Value, | ||
_ persistenceKey: PersistenceKeyDefault<Key>, | ||
fileID: StaticString = #fileID, | ||
line: UInt = #line | ||
|
@@ -328,15 +328,15 @@ extension SharedReader { | |
|
||
private struct LoadError: Error {} | ||
|
||
final class ValueReference<Value, Persistence: PersistenceReaderKey<Value>>: Reference, @unchecked | ||
Sendable | ||
final class ValueReference<Value, Persistence: PersistenceReaderKey<Value>>: Reference, | ||
@unchecked Sendable | ||
{ | ||
private let lock = NSRecursiveLock() | ||
private let persistenceKey: Persistence? | ||
#if canImport(Combine) | ||
private let subject: CurrentValueRelay<Value> | ||
#endif | ||
private var subscription: Shared<Value>.Subscription? | ||
private var subscription: Shared<Value>.Subscription! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we'd prefer for this to be a |
||
private var _value: Value { | ||
willSet { | ||
self.subject.send(newValue) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ import IssueReporting | |
/// wrapper. | ||
@dynamicMemberLookup | ||
@propertyWrapper | ||
public struct Shared<Value> { | ||
public struct Shared<Value: Sendable> { | ||
private let reference: any Reference | ||
private let keyPath: AnyKeyPath | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically this should now be: public struct Shared<Value: Sendable> { …and we should drop the conditional conformance below. But there are some weird interactions with |
||
|
||
|
@@ -275,7 +275,7 @@ public struct Shared<Value> { | |
|
||
private var snapshot: Value? { | ||
get { | ||
func open<Root>(_ reference: some Reference<Root>) -> Value? { | ||
func open<Root: Sendable>(_ reference: some Reference<Root>) -> Value? { | ||
@Dependency(\.sharedChangeTracker) var changeTracker | ||
return changeTracker?[reference]?.snapshot[ | ||
keyPath: unsafeDowncast(self.keyPath, to: WritableKeyPath<Root, Value>.self) | ||
|
@@ -284,7 +284,7 @@ public struct Shared<Value> { | |
return open(self.reference) | ||
} | ||
nonmutating set { | ||
func open<Root>(_ reference: some Reference<Root>) { | ||
func open<Root: Sendable>(_ reference: some Reference<Root>) { | ||
@Dependency(\.sharedChangeTracker) var changeTracker | ||
guard let newValue else { | ||
changeTracker?[reference] = nil | ||
|
@@ -344,7 +344,10 @@ extension Shared: _CustomDiffObject { | |
} | ||
|
||
extension Shared | ||
where Value: _MutableIdentifiedCollection { | ||
where | ||
Value: _MutableIdentifiedCollection, | ||
Value.Element: Sendable | ||
{ | ||
/// Allows a `ForEach` view to transform a shared collection into shared elements. | ||
/// | ||
/// ```swift | ||
|
@@ -408,11 +411,10 @@ extension Shared: MutableCollection | |
where Value: MutableCollection & RandomAccessCollection, Value.Index: Hashable { | ||
public subscript(position: Value.Index) -> Shared<Value.Element> { | ||
get { | ||
assertionFailure("Conformance of 'Shared<Value>' to 'MutableCollection' is unavailable.") | ||
return self[position, default: DefaultSubscript(self.wrappedValue[position])] | ||
fatalError("Conformance of 'Shared<Value>' to 'MutableCollection' is unavailable.") | ||
} | ||
set { | ||
self._wrappedValue[position] = newValue.wrappedValue | ||
fatalError("Conformance of 'Shared<Value>' to 'MutableCollection' is unavailable.") | ||
Comment on lines
+426
to
+429
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This subscript is unavailable so it should be ok to |
||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this temporarily so that we don't get flooded with warnings in test targets, but maybe we should keep this around until we've addressed all/most warnings in TCA proper, and then we can later concentrate on tests?