Body Macro formatted despite formatMode disabled

I'm having trouble using #sourceLocation inside a body macro expansion because the first level of nodes is always formatted.
Consider the following macro:

public struct SourceLocationMacro: BodyMacro {
    
    public static var formatMode: FormatMode { .disabled }
    
    public static func expansion(of node: AttributeSyntax, providingBodyFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax, in context: some MacroExpansionContext) throws -> [CodeBlockItemSyntax] {
        
        if let statements = declaration.body?.statements {
            var body: [CodeBlockItemSyntax] = []
            statements.forEach { statement in
                if let location = context.location(of: statement, at: .afterLeadingTrivia, filePathMode: .filePath) {
                    let transformed = CodeBlockItemListSyntax {
                        "\n#sourceLocation(file: \(location.file), line: \(location.line))"
                        statement
                        "\n#sourceLocation()"
                    }
                    body.append(contentsOf: transformed)
                }
            }
            return body
        } else {
            return []
        }
        
    }
    
}

If applied to the following code it works fine because let x is following the default indentation:

@SourceLocationMacro
func f() {
    let x: Int = 1
        ^ Immutable value 'x' was never used; consider replacing with '_' or removing it
}
// Macro Expansion
{
    #sourceLocation(file: "/Users/mateusrodrigues/Desktop/TriviaMacro/Sources/TriviaMacroClient/main.swift", line: 8)
    let x: Int = 1
    #sourceLocation()
}

But if I indent let x with a different number of spaces the diagnostic is reported in the wrong column because the code is automatically formatted.

@SourceLocationMacro
func f() {
        let x: Int = 1
        ^ Immutable value 'x' was never used; consider replacing with '_' or removing it
}
// Macro Expansion
{
    #sourceLocation(file: "/Users/mateusrodrigues/Desktop/TriviaMacro/Sources/TriviaMacroClient/main.swift", line: 8)
    let x: Int = 1
    #sourceLocation()
}

The problem still affects others level in less degree by shifting the indentation:

let transformed = CodeBlockItemListSyntax {
    "\n#sourceLocation(file: \(location.file), line: \(location.line))"
    statement
    "\n#sourceLocation()"
}
let item: CodeBlockItemSyntax = "let _ = { \(transformed) }()"
body.append(item)
@SourceLocationMacro
func f() {
        let x: Int = 1
                ^ Immutable value 'x' was never used; consider replacing with '_' or removing it

}
{
    let _ = { 
    #sourceLocation(file: "/Users/mateusrodrigues/Desktop/TriviaMacro/Sources/TriviaMacroClient/main.swift", line: 8)
            let x: Int = 1
    #sourceLocation() }()
}

This can be fixed by calculating the indentation using BasicFormat.inferIndentation(of:) and subtracting but not a pleasant experience and inferIndentation(of:) requires at least 3 lines of code so there's that too.

Is there anyway to disable this behavior? I expected that with formatMode set to disabled I would have totally control over the indentation.

Using FormatMode.disabled is definitely the right thing to do here since you want to control indentation.

Could you check if statement has leading trivia associated with it? In your second case, I would expect 8 spaces. If it does, could you check at which point the trivia get lost? Ie. does body have an indentation of 8 spaces?

That's correct. The leading trivia of statements is [newlines(1), spaces(8)]. The trivia is lost/changed after the return of expansion function.

I would expected the expansion to be:

{
let _ = { 
#sourceLocation(file: "/Users/mateusrodrigues/Desktop/TriviaMacro/Sources/TriviaMacroClient/main.swift", line: 8)
        let x: Int = 1
#sourceLocation() }()
}

Could you file an issue for this in the Swift-syntax repo?

1 Like
1 Like