Async marker shown for UIView properties in @resultBuilder DSL under Swift 6 strict concurrency

I maintain a framework called Adobels/UIViewKit, which provides a DSL for configuring UIViews and their Auto Layout constraints using @resultBuilder, allowing views to be written in code instead of relying on Storyboards.

When migrating to Swift 6 with strict concurrency enabled, I noticed that inside my DSL closures, UIView properties (like backgroundColor) are shown in autocomplete as if they were async. For example:

class ViewController: UIViewController {

     override func loadView() {
         super.loadView()

         view.ibAttributes {
             $0.backgroundColor = .systemBlue <--- async marker in autocomplete
         }
     }
}

This is confusing for end users of the framework because:

  • The code compiles and runs fine without await (everything runs on @MainActor as expected).

  • In Swift 5.10, the same code shows no async marker.

  • In Swift 6, the async marker appears only inside result-builder closures like the one above.

The relevant parts of my implementation look like this:

protocol UIViewDSL where Self: UIView { }

extension UIView: UIViewDSL { }


@resultBuilder

enum IBLayoutConstraintBuilder {

    static func buildBlock(_ components: [NSLayoutConstraint]...) -> [NSLayoutConstraint] {

        components.flatMap { $0 }

    }

    static func buildExpression(_ expr: Void) -> [NSLayoutConstraint] { [] }

}

extension UIViewDSL {

    @discardableResult

    public func ibAttributes(

        @IBLayoutConstraintBuilder _ block: (Self) -> [NSLayoutConstraint]

    ) -> Self {

        let constraints = block(self)

        // UIViewDSLEngine.shared.addConstraints(for: self, constraints: constraintsGenerated)

        return self

    }

}

Question

Is the async marker in autocomplete caused by something in my DSL design (e.g. how the @resultBuilder closure is typed), or is it a current limitation/bug in the compiler or IDE when combining @resultBuilder with @MainActor classes?

If it's the latter, is this already a known issue?

P.S. I tried adding @MainActor in all possible places to be more explicit, but that has not solved the issue. Autocompletion was tested in Xcode 16.4 and 26.0 Beta 4.

1 Like

I see this with Apple’s APIs all the time, it’s either an Xcode bug or it means something non obvious, like it’s isolated or something. I don’t think there’s anything for you to do.

1 Like

This async marker makes me wonder too. Here is a screenshot of the minimal reproducer:


It is clear that the @MainActor annotation causes this, there is no concurrency otherwise. No XCode either. Windows platform, Swift 6.1.3, Swift 6 language mode. Code suggestions are provided by sourcekit-lsp. Function xyzzy() was needed because the async marker does not appear in top level code.

That one kind of makes sense, as calling a @MainActor isolated function from a non isolated context would require an await. (It would be more useful if the autocomplete suggestion added the await for you rather than just indicating async. But I see it for any isolated value even when I'm in the same context. If you add @MainActor to xyzzy, does it still appear?

In this case it disappears.

I thought so too, and tried

await foo.bar()

And got error message 'await' in a function that does not support concurrency.

Ah sure, xyzzy isn't async itself.

If I make xyzzy async, it compiles, and await foo. shows code suggestion bar() without the async marker.

Huh, then I'm not sure what it would mean.

1 Like

Oops, that was because I temporarily annotated xyzzy as @MainActor and forgot to remove the annotation. The async annotation in code suggestions is shown in the async version of xyzzy (when it is nonisolated) for await foo.bar().

Yes, now I see that this async marker in code suggestions makes sense.

TBH I never thought about the fact that I may need to await a synchronous function (or property for that matter). But it looks like I should. I have a real life project that does not use Swift Concurrency, but has some classes annotated as @MainActor isolated, because they are UI-related.

It turns out that merely annotating a class @MainActor effectively turns it into an actor?

Here is the spiral of doom:

  • I don't want or need to use concurrency, but I need to work with UI
  • I annotate UI-related classes @MainActor
  • Hello actors and await and friends.

Why somehow if it’s the main purpose?

Which IMHO is good if you think in terms of isolation domain and effects.

It's a GUI framework with classes like Window, Button, etc. These are annotated @MainActor.
Things that are not GUI-related are non-isolated.

I don't think in these terms (but maybe I should).
I think in terms of single-threaded application, but since my project is a framework, the context from which things will be called is up to end user.