Section 4/101 menit

4. Cara Kerja Swift Macro (Teori Internal)

4. Cara Kerja Swift Macro (Teori Internal)

Pipeline Kompilasi dengan Macro

Ketika compiler menemukan macro, proses kompilasi berjalan seperti ini:

swift
Source Code (.swift)
        │
        ▼
   Swift Parser
        │  menghasilkan
        ▼
  Abstract Syntax Tree (AST)
        │
        ▼
  Macro Expansion Phase
   ┌────┴────────────────────────┐
   │  Compiler menemukan @Macro  │
   │  atau #macro               │
   └─────────────┬──────────────┘
                 │ kirim subtree AST ke
                 ▼
     Macro Plugin (executable)
     [berjalan dalam sandbox]
                 │ kembalikan AST baru
                 ▼
  Expanded AST (macro sudah diganti
  dengan kode yang di-generate)
        │
        ▼
  Type Checker & Optimizer
        │
        ▼
  Compiled Binary

AST (Abstract Syntax Tree)

AST adalah representasi terstruktur dari kode sumber sebagai pohon node. Setiap konstruk Swift (class, func, property, ekspresi) direpresentasikan sebagai node AST.

swift
// Kode sumber:
struct Point { var x: Int }

// Representasi AST (disederhanakan):
StructDecl(name: "Point") {
    VarDecl(
        name: "x",
        type: SimpleTypeIdentifier("Int")
    )
}

Macro menerima node AST sebagai input dan mengembalikan node AST baru. Ini mengapa macro type-safe — bukan manipulasi string seperti C preprocessor.

SwiftSyntax

SwiftSyntax adalah library Apple yang menyediakan API untuk membaca dan memodifikasi AST Swift. Semua macro plugin ditulis menggunakan SwiftSyntax.

swift
// Contoh: membaca nama semua stored property dari StructDecl
import SwiftSyntax
import SwiftSyntaxMacros

struct MyMacro: MemberMacro {
    static func expansion(
        of node: AttributeSyntax,
        providingMembersOf declaration: some DeclGroupSyntax,
        in context: some MacroExpansionContext
    ) throws -> [DeclSyntax] {

        // Baca semua stored property dari deklarasi struct/class
        let storedProperties = declaration.memberBlock.members
            .compactMap { $0.decl.as(VariableDeclSyntax.self) }
            .filter { $0.bindings.first?.accessorBlock == nil } // hanya stored, bukan computed

        // Generate kode baru berdasarkan property tersebut
        let initParams = storedProperties.map { variable -> String in
            let name = variable.bindings.first?.pattern.description ?? ""
            let type = variable.bindings.first?.typeAnnotation?.type.description ?? "Any"
            return "\(name): \(type)"
        }.joined(separator: ", ")

        return [
            "init(\(raw: initParams)) { \(raw: storedProperties.map { "self.\($0.bindings.first?.pattern.description ?? "") = \($0.bindings.first?.pattern.description ?? "")" }.joined(separator: "; ")) }"
        ]
    }
}

Sandbox Execution

Macro plugin berjalan sebagai proses terpisah yang terisolasi (sandbox). Ini berarti:

  • Macro tidak bisa membaca file sistem secara bebas
  • Macro tidak bisa melakukan network request
  • Macro tidak bisa memodifikasi state global
  • Jika macro crash, compiler memberikan error yang jelas — tidak crash seluruh build