Expected behavior for `AccessorMacro` when accessors already exist

I'm experimenting with macros for viability with code injection in variable bodies. Unfortunately, I'm getting different results from assertMacroExpansion in swift-syntax's main branch and Xcode's swift-frontend.

The following example doubles statements in computed properties:

extension InstrumentedMacro: AccessorMacro {
    public static func expansion<Context: MacroExpansionContext, Declaration: DeclSyntaxProtocol>(
        of node: AttributeSyntax,
        providingAccessorsOf declaration: Declaration,
        in context: Context
    ) throws -> [AccessorDeclSyntax] {
        
        guard
            let varDecl = declaration.as(VariableDeclSyntax.self),
            let binding = varDecl.bindings.first,
            let statements = binding.accessor?.as(CodeBlockSyntax.self)?.statements
        else {
            return []
        }
        
        return [
            AccessorDeclSyntax(
                accessorKind: "get",
                body: CodeBlockSyntax(statements: CodeBlockItemListSyntax(
                    Array(statements) + Array(statements)
                )))
        ]
    }
}

When running assertMacroExpansion, it successfully transforms the following:

struct MyView: View {
    @Instrumented
    var body: some View {
        Text("Hello, ")
        Text("World!")
    }
}

Into:

struct MyView: View {
    var body: some View  {
        get {
                Text("Hello, ")
                Text("World!")
                Text("Hello, ")
                Text("World!")
        }
    }
}

When I try running it in a in Xcode 15.0 (22181.22), I get the error message, /var/folders/5n/7jx33tyj6lj7jzz48r267wpw0000gn/T/swift-generated-sources/@__swiftmacro_6MyDemo0A4ViewV4body12InstrumentedfMa_.swift:1:1 Multiple definitions of symbol '$s6MyDemo0A4ViewV4bodyQrvg'

It appears that instead of rewriting the source like swift-syntax is doing, swift-frontend is generating the code, compiling the macro result and defining trying to add it to object code that already has the symbol defined.

Who has the correct long-term behavior here? Is it assertMacroExpansion, which replaces the existing accessor or swift-frontend, which is purely additive?

1 Like

It appears that instead of rewriting the source like swift-syntax is doing, swift-frontend is generating the code, compiling the macro result and defining trying to add it to object code that already has the symbol defined.

Your analysis is correct. What I would expect, is that both the compiler and assertMacroExpansion emit an error during the macro expansion, neither overriding the existing body nor emitting duplicate symbols which will only be diagnosed by the linker. I filed a bug report (rdar://111897711) internally and will keep you updated on its status.

1 Like

Thanks. It's a tad disappointing with what I'm trying to do but makes sense.