Capture list in a default value expression

I've run into another pattern that Swift 5.10 has flagged as unsafe. I'm really struggling to understand where the actor boundary here is. I've produced a somewhat reduced test case. Can anyone help me understand?

@MainActor
class A {
    init(_ fn: () -> Void) {
    }
}

class B {}

@MainActor
class C {
    let b = B()

    // Non-sendable type 'B' in asynchronous access to main actor-isolated property 'b' cannot cross actor boundary
    lazy var a = A({ _ = b })
}

Hmm, I see no actor boundary and I also see no warning under -strict-concurrency=complete with the latest 5.10 development snapshot from February 8th. Can you please run swiftc --version and let me know which swiftlang tag or commit hash you're using?

1 Like

I'm using Xcode 15 beta 3.

swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)

Target: arm64-apple-macosx14.0

Hm, my only suspicion for what might have resolved the warning ([5.10][Concurrency] Handle cases where a property initializer is subsumed by another property for `IsolatedDefaultValues`. by hborla · Pull Request #71034 · apple/swift · GitHub) is in Xcode 15 Beta 3. I'll install this to see if I can reproduce it.

Anyway, unless I am misunderstanding something, that closure is a non-Sendable closure formed on the @MainActor, so it's effectively @MainActor-isolated and accessing self.b should not be considered an asynchronous access. Something is causing the actor isolation checker to think that closure is nonisolated, in which case there would be an isolation boundary because B is not Sendable.

2 Likes

Is it possible that lazy is mucking up the analysis here somehow? Lazy initializers should run in the isolation context right?

1 Like

All of the examples in my project that show this warning are lazy.

Right, lazy initializers should be run in the computed property for the lazy variable, which is in the isolation domain. That’s why I suspected the isolated default argument fix revolved the warning, because lazy variables have their initializers marked as “subsumed”.

1 Like

Well, this is because I accidentally installed a development snapshot of main! I can reproduce the warning with the actual latest development snapshot of 5.10 from February 9th.

You can work around this by marking the closure parameter to A.init as @MainActor:

@MainActor
class A {
  init(_ fn: @MainActor () -> Void) {
  }
}

class B {}

@MainActor
class C {
  let b = B()

  lazy var a = A({ _ = b })
}

Funny enough, you can also work around this by changing that let to a var:

@MainActor
class A {
  init(_ fn: () -> Void) {
  }
}

class B {}

@MainActor
class C {
  var b = B()

  lazy var a = A({ _ = b })
}

My current suspicion is that this is some weird side effect of the way nonisolated was previously applied to actor-isolated let properties within the module, which was fixed on main by [Concurrency] `nonisolated` can only be applied to actor properties with `Sendable` type. by hborla · Pull Request #70909 · apple/swift · GitHub.

2 Likes

Fantastic, the world makes sense again!

I know that I can use the Swift evolution feature of swift.org to track the availability of proposals. Is there a way for me to know when a merged PR will become available?