UNPKG

@macroforge/mcp-server

Version:

MCP server for Macroforge documentation and code analysis

85 lines (66 loc) • 3.68 kB
# Declarative Macros Declarative macros let you define build-time code transformations entirely in TypeScript. They use pattern-matching syntax inspired by Rust's `macro_rules!`, adapted to TypeScript's grammar. No Rust knowledge and no native build step are required — you define the macro in the same `.ts` file where you use it. ## Quick Example ```typescript import { macroRules } from '@macroforge/core/rules'; const $vec = macroRules` () => [] ($($x:Expr),+) => { const __v: unknown[] = []; $( __v.push($x); )+ __v } `; // Usage — expanded at build time: const empty = $vec(); // → [] const xs = $vec(1, 2, 3); ``` The `$vec` definition is erased from the output, and every `$vec(...)` call site is replaced with the matched arm's expansion. Here `xs` becomes: ```typescript const xs = (() => { const __v$1 = []; __v$1.push(1); __v$1.push(2); __v$1.push(3); return __v$1; })(); ``` Two things are happening in that output that are worth understanding early: - The block body is wrapped in an **IIFE** because the call sits in expression position, and the trailing expression gets an injected `return`. See [Expansion & Hygiene](/docs/declarative-macros/expansion). - `__v` was renamed to `__v$1` by **hygiene**, so the macro can't collide with a `__v` at the call site. ## When to use declarative macros | Use a declarative macro when… | Use a [Rust macro](/docs/custom-macros) when… | | -------------------------------------------------------------------------- | ---------------------------------------------------------------- | | The transformation is syntactic — you can express it as pattern → template | You need to inspect resolved types, fields, or the type registry | | You want it to live next to the code that uses it | You want to publish reusable macros as a package | | You don't want a Rust toolchain in the build | You need to derive from a type's structure (`@derive(...)`) | Declarative macros run as a **pre-pass**, before derive and attribute macros. The full order is: declarative expansion → attribute macros → derive macros → codegen. That means a declarative macro can expand _into_ a class carrying `@derive(...)`, and the derive will still see it. ## The `$` prefix is required A macro binding's name **must** start with `$`. This is not a convention — discovery skips any binding that doesn't start with `$`, so `const vec = macroRules\`…\`` is simply not registered as a macro. The `$` prefix also marks a reserved namespace: a `$`-prefixed call whose callee isn't a declarative macro is dispatched to the [proc-macro fallback](/docs/declarative-macros/cross-file) instead. ## Section contents - **[Defining Macros](/docs/declarative-macros/defining)** — tag form, object form, erasure, scoping and shadowing - **[Patterns & Fragments](/docs/declarative-macros/patterns)** — all 11 fragment kinds and what each accepts - **[Repetitions & Backtracking](/docs/declarative-macros/repetitions)** — `$( … )sep*` on both sides of the arrow - **[Expansion & Hygiene](/docs/declarative-macros/expansion)** — contexts, IIFE wrapping, `__` renaming, composition - **[Type-Position Macros](/docs/declarative-macros/type-macros)** — `$Macro<T>` in type position - **[Reverse Monomorphization](/docs/declarative-macros/sharing)** — sharing a runtime helper instead of inlining - **[Cross-File Macros & Diagnostics](/docs/declarative-macros/cross-file)** — importing macros, and every error you can hit