Ah I see the problem with the body replacement and completion... that is tricky.
Thanks for explaining the composition as well, that's great.
Here's a hot take we just discussed off-thread I wanted to post here as well:
What if... instead of embracing the "wrapping" style, we make official the APIs to push / pop a task-local value, such that wrapping isn't necessary (at least for task-local setting macros).
The actual reason for "body wrapping" is that the only safe way to ensure task-locals are properly "scoped" is by wrapping them with the usual ServiceContext.withValue(...) { body }... However, internally a task-local is actually managed by a pair of "push" and "pop()" calls. They must be paired and trying to pop a task-local value from the bindings stack when there isn't anything results in a crash; and forgetting to pop "leaks" the value - which is why the only official API is the withValue to bind task-locals today.
However, we could make official these APIs:
enum ServiceContext {
@TaskLocal
static var current: ServiceContext?
}
// safe, existing API:
// ServiceContext.$current.withValue(...) { }
// new, unsafe, API we could offer:
ServiceContext.$current.unsafePushValue(...)
defer { ServiceContext.$current.unsafePopValue() }
Mis-managing the pop will result in crashes or incorrect runtime behavior. But since we can say these are unsafe APIs, that's acceptable, and especially if they're intended to be used by macros really, because then tracing can become a preamble macro:
@SpanPreamble
func hello() {
// let span = Tracing.startSpan(...)
// ServiceContext.$current.unsafePushValue(span.context)
// defer { ServiceContext.$current.unsafePopValue() }
}
The only question then remains about introducing names from a preamble macro -- if we could either use the Introducing names in body macros mechanism to have a preamble macro to say it'll introduce span: Span then we're golden.
Could this be an interesting direction?
I'd be happy to handle the proposal and implementation of the missing task-local APIs.
Body replacement macros probably remain useful for various reasons, but the less we can use them the better.