i2h3
1
I have implemented a custom macro to simplify signposting in methods. It worked fine all the time. Today I updated to Xcode 15.3 with Swift 5.10 and suddenly it results in a compiler error.
Usage:
#signpost
Declaration:
@freestanding(declaration, names: named(signpostID), named(signpostIntervalState))
public macro signpost() = #externalMacro(module: "RedactedLoggingMacros", type: "SignpostMacro")
Expansion:
let signpostID = signposter.makeSignpostID()
let signpostIntervalState = signposter.beginInterval(#function, id: signpostID)
defer {
signposter.endInterval(#function, signpostIntervalState)
}
Xcode indicates the following error on the defer block.
Expected macro expansion to produce a declaration
That has not happened before. Is this a bug or rather the fix for a previously existing bug?
I have this macro in use all over the codebase and now it causes compiler errors. 
There appears to be no alternative. A freestanding expression macro will nag about the declarations because an expression was expected. 
2 Likes
miburf
(Michael)
2
I had something similar. Wrapping into an "expression" worked:
_ = { /* your code */ }()
Your defer may not now be at the spot you want, but that worked to compile.
i2h3
3
grynspan
(Jonathan Grynspan)
4
Instead of a declaration macro, you could make an expression macro that takes a trailing closure:
@freestanding(expression)
public macro signpost<R>(_ body: () -> R) -> R = #externalMacro(module: "RedactedLoggingMacros", type: "SignpostMacro")
It would be callable like so:
#signpost {
// body here
}
And could expand to:
{
let signpostID = signposter.makeSignpostID()
let signpostIntervalState = signposter.beginInterval(#function, id: signpostID)
defer {
signposter.endInterval(#function, signpostIntervalState)
}
return \(bodyExpr)()
}()
This could maybe even be a function without needing a macro at all.
i2h3
5
Indeed, and that is similar to what the os framework offers, too.
Meanwhile I found a different way without a macro based on a struct to reduce it to this:
func whatever() async throws {
let signpost = signposter.begin()
defer {
signpost.end()
}
// Do something slow and risky here.
}
However, nothing beats the compactness of the original macro which I could use in 95% of the cases.
allevato
(Tony Allevato)
6
If you're going to do go that direction, it would be better to put a method that takes a closure on signposter instead, because that prevents the mistake of someone forgetting to call end() when they're done.