Open NachoSoto opened 2 months ago
cc @hborla
I tried creating a repro without Combine
but this does not trigger it, presumively because AsyncStream
is Sendable
:
@MainActor
final class Data {
public var stream: AsyncStream<Int> {
.init { _ in }
}
}
actor Actor {
private func f() async {
async let stream = await data.stream
for await _ in await stream {}
}
@MainActor private let data = Data()
}
Interestingly enough, this provides a proper error message:
@available(iOS 18.0, *)
@MainActor
final class Data {
public var stream: some AsyncSequence<Int, Never> {
AsyncStream { _ in }
}
}
@available(iOS 18.0, *)
actor Actor {
private func f() async {
// Non-sendable type 'some AsyncSequence<Int, Never>' in implicitly asynchronous access to main actor-isolated property 'stream' cannot cross actor boundary
async let stream = await data.stream
for await _ in await stream {}
}
@MainActor private let data = Data()
}
I am not seeing this error with the latest 6.0:
import Combine
@MainActor
final class Data {
enum E {}
@Published public private(set) var data: E?
public var stream: AsyncPublisher<Published<E?>.Publisher> {
$data.values
}
}
actor Actor {
private func f() async {
async let stream = await data.stream
for await _ in await stream {}
}
@MainActor private let data = Data()
}
Output:
gottesmm@Michaels-MacBook-Pro-155 tmp % xcrun -sdk macosx -toolchain swift swiftc -swift-version 6 -c test.swift
test.swift:18:24: error: non-sendable type 'AsyncPublisher<Published<Data.E?>.Publisher>' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary
16 | private func f() async {
17 | // Pattern that the region based isolation checker does not understand how to check. Please file a bug
18 | async let stream = await data.stream
| `- error: non-sendable type 'AsyncPublisher<Published<Data.E?>.Publisher>' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary
19 | for await _ in await stream {}
20 | }
Combine.AsyncPublisher:2:15: note: generic struct 'AsyncPublisher' does not conform to the 'Sendable' protocol
1 | @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
2 | public struct AsyncPublisher<P> : AsyncSequence where P : Publisher, P.Failure == Never {
| `- note: generic struct 'AsyncPublisher' does not conform to the 'Sendable' protocol
3 | public typealias Element = P.Output
4 | public struct Iterator : AsyncIteratorProtocol {
test.swift:2:1: warning: add '@preconcurrency' to treat 'Sendable'-related errors from module 'Combine' as warnings
1 |
2 | import Combine
| `- warning: add '@preconcurrency' to treat 'Sendable'-related errors from module 'Combine' as warnings
3 |
4 | @MainActor
test.swift:18:35: error: non-sendable type 'AsyncPublisher<Published<Data.E?>.Publisher>' in implicitly asynchronous access to main actor-isolated property 'stream' cannot cross actor boundary
16 | private func f() async {
17 | // Pattern that the region based isolation checker does not understand how to check. Please file a bug
18 | async let stream = await data.stream
| `- error: non-sendable type 'AsyncPublisher<Published<Data.E?>.Publisher>' in implicitly asynchronous access to main actor-isolated property 'stream' cannot cross actor boundary
19 | for await _ in await stream {}
20 | }
Combine.AsyncPublisher:2:15: note: generic struct 'AsyncPublisher' does not conform to the 'Sendable' protocol
1 | @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
2 | public struct AsyncPublisher<P> : AsyncSequence where P : Publisher, P.Failure == Never {
| `- note: generic struct 'AsyncPublisher' does not conform to the 'Sendable' protocol
3 | public typealias Element = P.Output
4 | public struct Iterator : AsyncIteratorProtocol {
Cool so I guess it can produce the error now. But I think it should still not fail? If you replace it with
let data = data
async let stream = await data.stream
Does it work?
No. What is happening here is that the async let in stream happens in a MainActor isolated context. The async let is actually nonisolated. So MainActor isolated state is being exposed to async let work.
That being said, I could imagine something like this:
@MainActor async let stream = await data.stream
but we don't have that today.
Reproduction
Expected behavior
This compiles like it did on Swift 5 and Swift 6 beta 1.
Environment
Additional information
Adding a local copy of
data
works around this: