Freestanding Macro that doesn't return an expression

I was trying to implement Assign if different using freestanding macro and I did succeed but kind of cheating using a closure because the freestanding macro is expected to return and expression.

#assign(b, to: a, if: !=) for example is expanded to:

{
    if a != b {
        a = b
    }
}()

Is this the only way to currently do this? Any plans to lift this restriction with another kind of freestanding macro?

@Douglas_Gregor

This is the only way to do this today. The early pitches for declaration macros had "code item" macros that could do this, and there's a partial implementation of the feature in the compiler behind an experimental feature flag (CodeItemMacros), but nobody has followed through with pitch/proposal/complete implementation. Personally, I haven't seen that many use cases for code-item macros vs. the other kinds, so I haven't looked into them further.

Doug

2 Likes

Hello,

I have a use case for this kind of feature that might be valuable.

It's not possible to possible to wrap os log calls because OSLogMessage cannot be initiliazed from a String variable, it forces us to duplicate log lines to both log into Unified logging system and to Crashlytics for example.

I would like to use a macro to expand the boilerplate code that perform those 2 calls. It seems that only CodeItemMacro can achieve this but I might be wrong...

2 Likes

I created a macro for this exact same use case, and ended up using the same immediately-executed closure strategy described in the original post here, in order to consolidate the two expressions down to a single one.

1 Like

(resurrecting an old thread)

I have a use case along the lines of:

#LogEnterExit()

Getting expanded to:

log("\(#function) Enter")
defer {
    log("\(#function) Exit")
}

Obviously it's more complex than that but that's the basic gist.

1 Like

Would completing the implementation of CodeItemMacros solve the need to add the {/* Generated code here */}() wrapper? Depending on the code before a macro containing the wrapper, it will occasionally get treated as a trailing closure and then fail with:
.../swift-generated-sources/@__REDEACTED_MANGLED_NAME_.swift:1:1 Function type mismatch, declared as '@convention(thin) @substituted <τ_0_0> (@in_guaranteed MySwiftUIView) -> (@out τ_0_0, @error any Error) for <()>' but used as '@convention(thin) (@guaranteed { var String }) -> ()'

My use case is basically the same as schwa's but the wrapper option is causing a few issues, even a compiler crash on Xcode 16.3 beta 1.

One work around is to change the wrapper to ;{/* Generated code here */}(). Note the leading semicolon.

But it would be really nice to not have to use a work around in a work around :)