diff --git a/Sources/ConcurrencyExtras/AsyncStream.swift b/Sources/ConcurrencyExtras/AsyncStream.swift index a79bd34..534b6fa 100644 --- a/Sources/ConcurrencyExtras/AsyncStream.swift +++ b/Sources/ConcurrencyExtras/AsyncStream.swift @@ -71,6 +71,12 @@ extension AsyncStream { } } + @available(*, deprecated, message: "Explicitly wrap the given async sequence with 'UncheckedSendable' first.") + @_disfavoredOverload + public init(_ sequence: S) where S.Element == Element { + self.init(UncheckedSendable(sequence)) + } + /// An `AsyncStream` that never emits and never completes unless cancelled. public static var never: Self { Self { _ in } @@ -94,4 +100,9 @@ extension AsyncSequence { public func eraseToStream() -> AsyncStream where Self: Sendable { AsyncStream(self) } + + @available(*, deprecated, message: "Explicitly wrap this async sequence with 'UncheckedSendable' before erasing to stream.") + public func eraseToStream() -> AsyncStream { + AsyncStream(UncheckedSendable(self)) + } } diff --git a/Sources/ConcurrencyExtras/AsyncThrowingStream.swift b/Sources/ConcurrencyExtras/AsyncThrowingStream.swift index 007a3eb..3df9bc4 100644 --- a/Sources/ConcurrencyExtras/AsyncThrowingStream.swift +++ b/Sources/ConcurrencyExtras/AsyncThrowingStream.swift @@ -26,6 +26,12 @@ extension AsyncThrowingStream where Failure == Error { } } + @available(*, deprecated, message: "Explicitly wrap the given async sequence with 'UncheckedSendable' first.") + @_disfavoredOverload + public init(_ sequence: S) where S.Element == Element { + self.init(UncheckedSendable(sequence)) + } + /// An `AsyncThrowingStream` that never emits and never completes unless cancelled. public static var never: Self { Self { _ in } @@ -53,4 +59,9 @@ extension AsyncSequence { public func eraseToThrowingStream() -> AsyncThrowingStream where Self: Sendable { AsyncThrowingStream(self) } + + @available(*, deprecated, message: "Explicitly wrap this async sequence with 'UncheckedSendable' before erasing to throwing stream.") + public func eraseToThrowingStream() -> AsyncThrowingStream { + AsyncThrowingStream(UncheckedSendable(self)) + } } diff --git a/Sources/ConcurrencyExtras/UncheckedSendable.swift b/Sources/ConcurrencyExtras/UncheckedSendable.swift index 2747c9d..1367e9d 100644 --- a/Sources/ConcurrencyExtras/UncheckedSendable.swift +++ b/Sources/ConcurrencyExtras/UncheckedSendable.swift @@ -55,6 +55,15 @@ public struct UncheckedSendable: @unchecked Sendable { } } +extension UncheckedSendable: AsyncSequence where Value: AsyncSequence { + public typealias AsyncIterator = Value.AsyncIterator + public typealias Element = Value.Element + + public func makeAsyncIterator() -> AsyncIterator { + value.makeAsyncIterator() + } +} + #if swift(>=5.10) @available(iOS, deprecated: 9999, message: "Use 'nonisolated(unsafe) let', instead.")@available( macOS, deprecated: 9999, message: "Use 'nonisolated(unsafe) let', instead." diff --git a/Tests/ConcurrencyExtrasTests/AsyncStreamTests.swift b/Tests/ConcurrencyExtrasTests/AsyncStreamTests.swift index 8971539..28e1946 100644 --- a/Tests/ConcurrencyExtrasTests/AsyncStreamTests.swift +++ b/Tests/ConcurrencyExtrasTests/AsyncStreamTests.swift @@ -4,33 +4,52 @@ @available(iOS 15, *) private let sendable: @Sendable () async -> AsyncStream = { - NotificationCenter.default - .notifications(named: UIApplication.userDidTakeScreenshotNotification) - .map { _ in } - .eraseToStream() + UncheckedSendable( + NotificationCenter.default + .notifications(named: UIApplication.userDidTakeScreenshotNotification) + .map { _ in } + ) + .eraseToStream() + } + + @available(iOS 15, *) + private let sendableInitializer: @Sendable () async -> AsyncStream = { + AsyncStream( + UncheckedSendable( + NotificationCenter.default + .notifications(named: UIApplication.userDidTakeScreenshotNotification) + .map { _ in } + ) + ) } @available(iOS 15, *) private let mainActor: @MainActor () -> AsyncStream = { - NotificationCenter.default - .notifications(named: UIApplication.userDidTakeScreenshotNotification) - .map { _ in } - .eraseToStream() + UncheckedSendable( + NotificationCenter.default + .notifications(named: UIApplication.userDidTakeScreenshotNotification) + .map { _ in } + ) + .eraseToStream() } @available(iOS 15, *) private let sendableThrowing: @Sendable () async -> AsyncThrowingStream = { - NotificationCenter.default - .notifications(named: UIApplication.userDidTakeScreenshotNotification) - .map { _ in } - .eraseToThrowingStream() + UncheckedSendable( + NotificationCenter.default + .notifications(named: UIApplication.userDidTakeScreenshotNotification) + .map { _ in } + ) + .eraseToThrowingStream() } @available(iOS 15, *) private let mainActorThrowing: @MainActor () -> AsyncThrowingStream = { - NotificationCenter.default - .notifications(named: UIApplication.userDidTakeScreenshotNotification) - .map { _ in } - .eraseToThrowingStream() + UncheckedSendable( + NotificationCenter.default + .notifications(named: UIApplication.userDidTakeScreenshotNotification) + .map { _ in } + ) + .eraseToThrowingStream() } #endif