Compiler ignores `@concurrent` and `nonisolated(nonsending)` attributes when a closure captures `isolated` parameter

I doubt your explanation. If fn1 in the following example was in non-actor domain, then bar would have run on global executor.

class NS {}

func bar() {}

actor A {
    let ns = NS()

    func foo() {}

    func test() async {
        let fn1: @concurrent () async -> Void = {
            self.assertIsolated() // OK
            bar()
            await self.foo() // `await` isn't needed
        }        
        let fn2: @MainActor () async -> Void = {
            MainActor.shared.assertIsolated() // OK
            bar()
            await self.foo()
        }
        await fn1()
        await fn2()
    }
}

let a = A()
await a.test()

The above code actually produces a warning about await self.foo() line fn1:

warning: no 'async' operations occur within 'await' expression

That indicates compiler thinks fn1 is self-isolated, not nonisolated.

I guess this behavior is an unexpected side effect of SE-0461. Before Swift 6.2, there was no way to explicitly specify a closure is nonisolated. A nonisolated closure could only be inferred and, when the closure captured isolated parameter, it's inferred to have the same isolation as the parameter. All worked perfectly. After SE-0461 it's possible to specify @concurrent and nonisolated(nonsending) explicitly and thus the conflict.

// Try this on releases before 6.2
let fn1: nonisolated () async -> Void = { }  // invalid
let fn2: @MainActor () async -> Void = { }  // ok

BTW, the more I think about it, the more I think a closure capturing actor-isolated values (including properties, local variables, and parameters) should be Sendable, for the same reason why a global actor isolated closure is Sendable. The only difference between them is that the former runs on an instance of a custom actor and the latter runs on the shared instance of a global actor.