I have been playing with the isolation macro quite a bit.
I am almost convinced that it only knows about the static isolation, but the documentation does not say anything about this.
Documentation
Macro
isolation()
Produce a reference to the actor to which the enclosing code is isolated, or nil if the code is nonisolated.
...
@freestanding(expression)
macro isolation<T>() -> T
Overview
If the type annotation provided for #isolation is not (any Actor)?, the type must match the enclosing actor type. If no type annotation is provided, the type defaults to (any Actor)?.
//
// IsolationInspector.swift
//
//
enum IsolationInspector {
@inline(always)
static func inspect (f: String = #function, prefix: String = "", line: Int = #line, _ u: (any Actor)? = #isolation) {
let x = type (of: self)
if let u {
print ("\(x):", "\(f): \(line): \(prefix): [\(u)]")
}
else {
print ("\(x):", "\(f): \(line): \(prefix): [undefined]")
}
}
}
The output:
// main () -> A1.run () -> w (x)
IsolationInspector.Type: main(): 6: 1: [Swift.MainActor]
IsolationInspector.Type: run(): 66: A1: [IsolationInspector.A1] // in actor A1, call w (_:)
IsolationInspector.Type: w(_:): 74: A1: run(): [undefined] // in sync w (_:), called by actor A1
IsolationInspector.Type: run(): 69: A1: [IsolationInspector.A1] // back in actor A1
// Legend:
// A1: an actor
// w(x): a synchronous function
// [x] means 'x` is the isolation provided by the isolation macro
Why is the isolation undefined inside the w () function, called by actor A1?
You are correct, #isolation resolves to the current static isolation.
In this example, w is a nonisolated function so #isolation is nil there. There is no way to retrieve the isolation inherited by a nonisolated synchronous function that I know of. And that is what's happening when the actor invokes w.
You can get at the isolation of an asynchronous function with the macro if you have NonisolatedNonsendingByDefault enabled.
Non-isolated synchronous functions have no notion of dynamic isolation; using the #isolation macro inside those functions will always resolve to nil.
SE-0461 does mention this explicitly:
Note that this introduces a semantic difference compared to synchronous nonisolated functions, where there is no implicit isolated parameter and #isolation always expands to nil. For example, the following program prints nil:
func printIsolation() {
let isolation = #isolation
print(isolation)
}
@main
struct Program {
// implicitly isolated to @MainActor
static func main() async throws {
printIsolation()
}
}
Yeah, the implementation of nonisolated(nonsending) is close to this:
func p (_ x: String, isolatedParam: isolated (any Actor)?) async {
// here, #isolation will return the value of isolatedParam
}
(the difference is that a nonsending function cannot accept arbitrary actors, it can only be the caller)
So, more specifically it is not just because this function is nonisolated. That's not enough. It is because it is a (asynchronous) nonisolated (nonsending) function which inherits the isolation of the caller and also makes that isolation available inside the function body statically.
As you can see, both variants are quite similar. While there are important differences between the two, both have an @sil_isolated parameter annotation, meaning that p, if isolated at all, becomes isolated to the actor that is passed in.
Now, let's look at the apply-side SIL (again, somewhat simplified and made-up) when p is called from a @MainActor isolated context and the argument passed to isolatedParam is the result of the #isolation macro:
I'm sort of re-interpreting your question now. Perhaps the confusion is around being simultaneously "nonisolated" but also isolated to something at runtime?
I agree this is strange. And it's doubly so now that nonisolated(nonsending) is so close to an isolated parameter.
Here's my 30-second-of-thinking attempt at an explanation:
"nonisolated" is a static property that says, basically, "I'm making no promises about what actor, if any, I run on". But this does mean that, at runtime, there could in fact be an actor doing the executing. That actor is not statically accessible to synchronous functions, but it is to nonisolated(nonsending) functions (via the #isolation macro).
Edit: and to return to your original question, I think it probably does make sense to tweak that documentation so it more explicitly indicate that, being a compile-time construct, the macro can only see statically-visible isolation.