Adding a boilerplate property to every model — an id, a CodingKeys enum, a logging hook — used to mean either hand-writing it everywhere or reaching for a code generation script that runs outside the build. A macro does the same expansion, but as part of compilation, with full type-checker visibility into the result.
#The new way
The expansion logic lives in a separate compiler-plugin target, built against SwiftSyntax:
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxMacros
public struct AddIdentifiableMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
["let id = UUID()"]
}
}
@main
struct MyMacrosPlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [
AddIdentifiableMacro.self,
]
}
The client-facing package only exposes the macro's declaration, pointing at that plugin by name:
@attached(member, names: named(id))
public macro AddIdentifiable() = #externalMacro(module: "MyMacrosPlugin", type: "AddIdentifiableMacro")
Call sites just apply the attribute:
import Foundation
@AddIdentifiable
struct Order {
let total: Double
}
let order = Order(total: 42.0)
print(order.id)
#Why it matters
providingMembersOfgets the full syntax tree ofOrder, so the macro could inspect existing properties instead of blindly adding one@attached(member, names: named(id))declares up front exactly which names the macro can introduce, so Xcode can offer completion foridbefore expansion even runs- Expansion happens at compile time and produces real Swift source the type checker validates — a malformed expansion is a build error, not a runtime surprise
- The macro implementation lives in a separate SwiftSyntax-based target, kept out of the app's runtime binary entirely
#Gotcha
Macro expansions are debugged by reading generated source, not by stepping through the macro implementation at the call site — use Xcode's "Expand Macro" action (or swift build with the expansion flags) to see exactly what code a given annotation produced. A macro that compiles fine in isolation can still fail at a specific call site if the declaration it's attached to doesn't match what the macro assumed (wrong property count, missing type annotation), and that failure surfaces as a diagnostic on the macro attribute, not inside the macro's own implementation file.