Skip to content

Typed Macros

Chris Michael edited this page Sep 2, 2026 · 2 revisions

Pudu

Typed Macros

A Pudu macro transforms parsed syntax. Its parameters declare the syntactic category accepted at each call site, and its call spelling remains visibly distinct from a function call.

macro twice(value: expr) = {
  let held = value
  held + held
}

fn main() -> Int {
  twice!(3)
}

The expr annotation means the argument must parse as an expression. Calls use !, so reading source does not require prior knowledge of which declarations are macros.

Parameter categories

Category Accepts
expr One parsed expression.
ident One identifier.
block One parsed block.

Arity is exact. Arguments are parsed before substitution, preserving the grouping written by the caller. Pudu does not expose an untyped token stream or a repetition matcher in the current macro system.

Hygiene

Bindings introduced by an expansion are renamed for that expansion. In twice, the internal name held cannot capture a caller binding with the same spelling, and a caller name cannot capture it. The renaming follows lexical scope, including the success-only bindings of if let and the body-only bindings of while let.

Hygiene applies to introduced bindings, not to the public names intentionally passed as ident arguments. An identifier parameter is the explicit boundary for syntax whose name comes from the call site.

Expansion order and bounds

Expansion precedes ordinary resolution and type checking. The expanded syntax must therefore be a valid program under the same rules as handwritten syntax. Expansion is bounded; recursive expansion that exhausts the bound is diagnosed at the macro call span.

The current syntax nodes retain the call span used for diagnostics. A full provenance chain that simultaneously exposes every definition and expansion site is not implemented.

Restricted power

Macros cannot read files, inspect environment variables, access the network, call arbitrary host code, or reach compiler internals. They are syntax transformers, not build scripts. This keeps a macro invocation reproducible from its source and arguments.

Choosing a function instead

Use a function when ordinary values and types can express the operation. Functions compose with generic code, trait bounds, first-class function values, and normal tooling without expansion. Use a macro when syntax itself is the input—for example, when a block or identifier must be placed into a generated construct.

twice! is pedagogical, not an argument that arithmetic should normally be a macro. A generic or ordinary function is the clearer abstraction when it can state the same contract.

Related

Clone this wiki locally