Macros

30.1 Using Macros (Swift 5.9+)

Freestanding expression #name(...) Produces an expression #URL, #Predicate, #expect #Preview (SwiftUI previews) Adds declarations next to it Async/completion-handler variants Adds getters/setters Property storage macros Adds attributes to members Adds conformances/extensions @Model, @CodingKeys libraries @attached(body) (6.0) Generates/augments function bodies

// Freestanding expression macros start with #
let url = #URL("https://swift.org")                 // a compile-time validated URL (common custom macro)
print(#function, #file, #line, #column)            // built-in literal "macros"
#warning("Implement caching")
#error("Unsupported platform")                     // (inside #if conditions)
let predicate = #Predicate<User> { $0.age > 18 }   // Foundation/SwiftData
#expect(result == 4)                                // Swift Testing
let regex = #/(\d+)-(\d+)/#                         // regex literal (not a macro, but compile-time checked)

// Attached macros start with @
@Observable                                         // Observation framework: generates tracking code
final class Store {
    var items: [String] = []
}

@Model                                              // SwiftData
final class Note { var title = "" ; init() {} }

@Test func addition() { #expect(1 + 1 == 2) }       // Swift Testing

30.2 Writing a Macro

Macros run in a sandboxed compiler plugin, operate on syntax (SwiftSyntax), and are type-checked. Their expansions are visible in Xcode ("Expand Macro"). Test them with assertMacroExpansion.

swift package init --type macro --name MyMacros
// Declaration (in the library target)
@freestanding(expression)
public macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "MyMacrosMacros", type: "StringifyMacro")

// Implementation (in the macro target, using SwiftSyntax)
import SwiftSyntax
import SwiftSyntaxMacros
import SwiftCompilerPlugin

public struct StringifyMacro: ExpressionMacro {
    public static func expansion(of node: some FreestandingMacroExpansionSyntax,
                                 in context: some MacroExpansionContext) -> ExprSyntax {
        guard let argument = node.arguments.first?.expression else {
            fatalError("compiler bug: the macro does not have any arguments")
        }
        return "(\(argument), \(literal: argument.description))"
    }
}

@main
struct MyMacrosPlugin: CompilerPlugin {
    let providingMacros: [Macro.Type] = [StringifyMacro.self]
}

// Usage
let (value, code) = #stringify(2 + 3)    // (5, "2 + 3")