Preserve Whitespace in Swift Macro Convenience Constructor?

Question

Suppose I have an AccessorMacro like this:

enum Foo: AccessorMacro
{
    static func expansion(...) throws -> [SwiftSyntax.AccessorDeclSyntax]
    {
        return [
            """
            get {
               print("got")
            }

            """,
     
            ...
        ]
    }
}

See that extra blank line at the bottom of the multiline string? That gets removed when this string is converted into SwiftSyntax. Is there anything I can do to keep it?

Failed Attempt

If I use this instead of the string:

try AccessorDeclSyntax("get") {
    """
    print("got")
    """
}
.with(.\trailingTrivia, .newlines(1))

The macro builds successfully but fails to expand. The compiler complains that the macro returned a DeclSyntax instead of the expected AccessorDeclSyntax. That happens even without the attempt to add trailingTrivia. Maybe I'm just not using AccessorDeclSyntax correctly?

I tried your example using SwiftSyntax 5.9 and got the same error.

It seems a get AccessorDeclSyntax is not suitable to be constructed using init(_ header:, bodyBuilder:), because inside its implementation, DeclSyntax does not accept "get {}":

arsing a `DeclSyntax` node from string interpolation produced the following parsing errors.
Set a breakpoint in `SyntaxParseable.logStringInterpolationParsingError()` to debug the failure.
1 │ get { }
  │ │      ╰─ error: expected declaration
  │ ╰─ error: unexpected code 'get { }' before declaration

We can bypass this problem using another initializer, for example:

try AccessorDeclSyntax(
    """
    get {
        print("got")
    }
    """
)
1 Like

That doesn't seem to be a valid initializer for AccessorDeclSyntax. It generates compiler errors.

The code below is valid syntax, but the trailing newlines are again dropped in the macro expansion, which reduces readability in the expanded macro:

AccessorDeclSyntax(stringLiteral:
   """
   get {
       print("got")
   }
   """
)
.with(\.trailingTrivia, .newlines(2))

I cannot, for the life of me, seem to get leading or trailing trivia to be retained.

That doesn't seem to be a valid initializer for AccessorDeclSyntax

You're right, I mistyped, the correct initializer should be ".init(_:)".

I've observed this general rule of macro expansion, that the compiler is free to adjust the leading and trailing trivia of the syntax we provide, but it will try not to touch internal trivia of sub syntax.

Can I ask specifically why do you want to control the appearance of expanded code?

Macros should generate nice, neat code. If they spew out gobbledygook that's difficult for humans to parse, it makes macros even more of a black box than they already are. The accessors I'm synthesizing are complex, so the resulting wall-of-text is harder to parse than it would be if I could control spacing.

I'll just have to live with it, I guess.

To my mind, if a sentient human developer takes the time to explicitly state: "I want 2 newlines after this declaration," then the automated formatter should shut up and follow directions. Human decisions should trump automated ones.

(But that is rarely the approach Apple chooses—anywhere. Which is why my iPhone constantly changes "we're" to "were" and "we'll" to "well", despite the fact that I took the time to manually type out that apostrophe.)