Macros are the lightest-weight metaprogramming tool in Lean 4. They operate purely onDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/leanprover/lean4/llms.txt
Use this file to discover all available pages before exploring further.
Syntax trees — no types, no environment — and are processed before elaboration begins. Because macros are hygienic and composable, they are the right tool for notation, syntactic sugar, and lightweight domain-specific languages.
The MacroM Monad
Every macro runs in MacroM, a stripped-down monad that provides:
- Hygiene: fresh name generation via macro scopes (
withFreshMacroScope). - Error reporting:
Macro.throwErrorandMacro.throwErrorAt. - Exception:
Macro.Exception.unsupportedSyntaxto signal that this rule doesn’t handle the input (allowing fallthrough to the next rule).
MacroM deliberately does not have access to the Environment or type information. This keeps macros fast and compositional.
macro_rules: Pattern-Matching Macros
macro_rules lets you write a collection of syntax-to-syntax rewrite rules. Each alternative is tried in order; on a match the rule fires and produces new syntax that re-enters macro expansion.
If no alternative matches,
macro_rules automatically throws Macro.Exception.unsupportedSyntax, which tells the elaborator to try the next registered macro for this syntax kind.macro: The Shorthand Form
For single-rule macros with a fixed syntactic form, the macro command combines syntax declaration and rule in one:
macro desugars to a syntax declaration followed by a macro_rules rule — you can see this by inspecting the generated code with set_option pp.all true.
Syntax Quotation
Syntax quotation is the primary way to construct and deconstructSyntax values. Backtick-paren notation `(…) lifts Lean source text into a Syntax value at compile time.
Building Syntax
`(term| …), `(tactic| …), `(command| …), etc.
Pattern Matching on Syntax
Syntax quotation works in patterns too:Antiquotation: Splicing Values In
Antiquotation (the$ escape) lets you splice a Syntax value into a quotation.
| Notation | Meaning |
|---|---|
$x | Splice the syntax variable x |
$(e) | Splice the expression e (evaluated at macro-expansion time) |
$x,* | Splice a comma-separated list from an array x |
$x:ident | Match/splice with a category annotation |
$[$xs]* | Splice an array of syntax nodes with no separator |
$[$xs],* | Splice an array with comma separators |
Hygienic Macros and Auto-Generated Names
Lean 4 macros are hygienic by default. When a macro introduces a new binding, it gets a fresh macro scope suffix that prevents capture of variables from the call site.tmp becomes something like tmp@[macro_scope_42]. The surface syntax still shows tmp in error messages, but the kernel sees the scoped name.
If you want to intentionally capture a name (anti-hygienic), use mkIdent without a fresh scope:
Error Reporting in MacroM
Macro.throwError for errors not tied to a specific syntax node, and Macro.throwErrorAt stx msg to attach the error to a source range.
Complete Example: unless and swap!
Notation: Operators and Mixfix
For operator-like syntax, thenotation, infixl, infixr, prefix, and postfix commands are thin wrappers around macro:
+ is 65, * is 70, function application is 1024. Higher numbers bind tighter.
The @[macro myMacroName] Attribute
The @[macro k] attribute on a Macro function registers it as a handler for the syntax node kind k, exactly as macro_rules does internally:
unsupportedSyntax, the next one is tried.