Skip to content
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

More support for Swift 6. #31

Merged
merged 1 commit into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Package@swift-6.0.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ let package = Package(
"ConcurrencyExtras"
]
),
]
],
swiftLanguageVersions: [.v6]
)

#if !os(Windows)
Expand Down
12 changes: 6 additions & 6 deletions Sources/ConcurrencyExtras/AsyncStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,16 @@ extension AsyncStream {
/// ```
///
/// - Parameter sequence: An async sequence.
public init<S: AsyncSequence>(_ sequence: S) where S.Element == Element {
public init<S: AsyncSequence>(_ sequence: S) where S.Element == Element, S: Sendable {
let lock = NSLock()
var iterator: S.AsyncIterator?
let iterator = UncheckedBox<S.AsyncIterator?>(wrappedValue: nil)
self.init {
lock.withLock {
if iterator == nil {
iterator = sequence.makeAsyncIterator()
if iterator.wrappedValue == nil {
iterator.wrappedValue = sequence.makeAsyncIterator()
}
}
return try? await iterator?.next()
return try? await iterator.wrappedValue?.next()
}
}

Expand All @@ -79,7 +79,7 @@ extension AsyncStream {
extension AsyncSequence {
/// Erases this async sequence to an async stream that produces elements till this sequence
/// terminates (or fails).
public func eraseToStream() -> AsyncStream<Element> {
public func eraseToStream() -> AsyncStream<Element> where Self: Sendable {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand this, but unfortunately this means that because Combine is stuck in the past, you can't do this anymore:

publisher.values.eraseToStream()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does a @preconcurrency import help with that? Or worst case, a @retroactive @unchecked Sendable conformance for the publisher in question?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does a @preconcurrency import help with that?

Nope

a @retroactive @unchecked Sendable conformance for the publisher in question?

Yeah but that would silence all other issues related to mis-using Publishers.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any chance of relaxing this requirement? We'll be stuck on 1.1.0 otherwise to be able to keep supporting Combine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NachoSoto We recently introduced a conditional conformance to UncheckedSendable here: #44

Can you use that?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's perfect thank you!

Ended up doing this (AnyAsyncSequence is our own type to deal with the Failure associatedtype availability):

extension Publisher where Output: Sendable {
  public func eraseToAnyAsyncSequence() -> AnyAsyncSequence<Output> {
    // Combine does not support Swift concurrency, so we rely on `ConcurrencyExtras`
    // allowing this.
    UncheckedSendable(values).eraseToAnyAsyncSequence()
  }
}

AsyncStream(self)
}
}
12 changes: 6 additions & 6 deletions Sources/ConcurrencyExtras/AsyncThrowingStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ extension AsyncThrowingStream where Failure == Error {
/// terminates, rethrowing any failure.
///
/// - Parameter sequence: An async sequence.
public init<S: AsyncSequence>(_ sequence: S) where S.Element == Element {
public init<S: AsyncSequence>(_ sequence: S) where S.Element == Element, S: Sendable {
let lock = NSLock()
var iterator: S.AsyncIterator?
let iterator = UncheckedBox<S.AsyncIterator?>(wrappedValue: nil)
self.init {
lock.withLock {
if iterator == nil {
iterator = sequence.makeAsyncIterator()
if iterator.wrappedValue == nil {
iterator.wrappedValue = sequence.makeAsyncIterator()
}
}
return try await iterator?.next()
return try await iterator.wrappedValue?.next()
}
}

Expand All @@ -34,7 +34,7 @@ extension AsyncThrowingStream where Failure == Error {
extension AsyncSequence {
/// Erases this async sequence to an async throwing stream that produces elements till this
/// sequence terminates, rethrowing any error on failure.
public func eraseToThrowingStream() -> AsyncThrowingStream<Element, Error> {
public func eraseToThrowingStream() -> AsyncThrowingStream<Element, Error> where Self: Sendable {
AsyncThrowingStream(self)
}
}
6 changes: 6 additions & 0 deletions Sources/ConcurrencyExtras/Internal/UncheckedBox.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
final class UncheckedBox<Value>: @unchecked Sendable {
var wrappedValue: Value
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}
2 changes: 2 additions & 0 deletions Sources/ConcurrencyExtras/LockIsolated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ extension LockIsolated where Value: Sendable {
}
}

#if swift(<6)
@available(*, deprecated, message: "Lock isolated values should not be equatable")
extension LockIsolated: Equatable where Value: Equatable {
public static func == (lhs: LockIsolated, rhs: LockIsolated) -> Bool {
Expand All @@ -106,6 +107,7 @@ extension LockIsolated: Hashable where Value: Hashable {
hasher.combine(self.value)
}
}
#endif

extension NSRecursiveLock {
@inlinable @discardableResult
Expand Down
7 changes: 4 additions & 3 deletions Sources/ConcurrencyExtras/MainSerialExecutor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@
private typealias Hook = @convention(thin) (UnownedJob, Original) -> Void

private var swift_task_enqueueGlobal_hook: Hook? {
get { _swift_task_enqueueGlobal_hook.pointee }
set { _swift_task_enqueueGlobal_hook.pointee = newValue }
get { _swift_task_enqueueGlobal_hook.wrappedValue.pointee }
set { _swift_task_enqueueGlobal_hook.wrappedValue.pointee = newValue }
}
private let _swift_task_enqueueGlobal_hook: UnsafeMutablePointer<Hook?> =
private let _swift_task_enqueueGlobal_hook = UncheckedSendable(
dlsym(dlopen(nil, 0), "swift_task_enqueueGlobal_hook").assumingMemoryBound(to: Hook?.self)
)
#endif
Loading