I'm working on converting a legacy codebase to modern concurrency. One of the helper methods in the project is runOnMainThreadAsync, which runs its argument immediately if on the main thread and asynchronously otherwise. The initial implementation is something like this:
Most of the time this is called, it's accessing some API that can only be used from the main thread, i.e. @MainActor functions. As such I need to annotate this callback as @MainActor. However, I'm unable to find a spelling that doesn't diagnose about the else branch. Here's what I've come up with so far:
pointing at the { on the line Task { @MainActor in. What's being illegally sent here?
The task isn't being sent, as it's not being returned, but even then tasks are sendable
The work is being sent to the actor it's isolated to so there's nothing unsafe here, right?
I suspect this is an overzealous or misleading diagnosis, but I'm running out of ideas. Is it just not possible to safely schedule an @escaping @MainActor closure from a nonisolated method?
Try adding @Sendable to the closure definition. I think there's bug in the Swift 5 mode where @MainActor doesn't properly infer Sendable (even with complete checking), but in Swift 6 mode it does.
Yep, @Sendable did it, thanks! (With that annotation I can even leave it as DispatchQueue.main.async(execute: work), which is mildly surprising, but whatever)
But execute isn't @MainActor, only @Sendable, right? Adding the @MainActor annotation would be dependent on the value of self, which I thought wasn't expressible under Swift's current type system.
The compiler is hard coded to recognize that DispatchQueue.main requires @MainActor (the implementation of which is hilarious). Unfortunately this means your own API wrapping the main queue lose the special capability, which is very annoying.
It's not a bug, it's just behind a different upcoming feature. You need to enable -enable-upcoming-feature GlobalActorIsolatedTypesUsability. The reason it's not enabled under -strict-concurrency=complete is because it would be a source break for code that had already adopted complete concurrency checking. The same is true for InferSendableFromCaptures and DisableOutwardActorInference.
It's a proper upcoming feature implemented in Swift 6.0. Thanks for pointing out that there's a missing toggle in Xcode build settings. It can still be enabled via "Other Swift Flags"