beck_macro/
lib.rs

1//! Macro expansion, hygienic from the first commit.
2//!
3//! [`docs/08-roadmap.md`](../../../../docs/08-roadmap.md) Phase 1: "Macro expander with hygiene —
4//! **from the start** (§2.4); retrofitting hygiene is a rewrite." `docs/02-syntax.md` §2.4:
5//! "Identifiers introduced inside a `quote` get a fresh hygiene scope in `Node.meta`; capture is
6//! possible but must be explicit (`inject(name)`)."
7//!
8//! # The algorithm
9//!
10//! Flatt's *sets of scopes*, which is the model Racket settled on after, as §2.4 puts it, "Scheme's
11//! 20-year history here". Each expansion step:
12//!
13//! 1. mints a fresh [`Scope`];
14//! 2. adds it to the macro's **input** — every identifier the call site supplied;
15//! 3. substitutes the arguments into the template;
16//! 4. **flips** the scope over the whole result.
17//!
18//! The flip is what makes it work. Identifiers that came from the call site had the scope added in
19//! step 2 and lose it again in step 4, so they mean what they meant where they were written.
20//! Identifiers the template introduced never had it, so they gain it — and a binding carrying a
21//! scope the call site does not have is invisible to the call site's references. Capture becomes
22//! impossible in both directions rather than unlikely.
23//!
24//! Resolution itself lives in `beck-types`: a binding is a candidate for a reference exactly when
25//! `binding.scopes ⊆ reference.scopes`, and the most specific candidate wins.
26//!
27//! # What a macro body may do
28//!
29//! Anything a pure Beck function may do. [`interp`] is the compile-time interpreter §2.4 calls for
30//! — bindings, `if`, `for`, `while`, lambdas, calls to the module's own `def`s and to the pure
31//! part of the prelude — in a **capability-restricted** environment: there is no name for a file,
32//! a socket, a clock or a process, and the prelude's effectful primitives are refused by name so
33//! that reaching for one is a diagnostic rather than a spelling mistake.
34//!
35//! `quote:` is the form whose value is *syntax*, and `$e` inside one is an ordinary expression
36//! whose value is reflected back into the template — so `$x` where `x` is a parameter is the
37//! caller's code, and `$(n * 2)` is a literal. That is the whole difference from the template
38//! expander this used to be: a `let` in a macro body now *computes* rather than substituting.
39//!
40//! And `refuse("…")` is how a body says it has no rule for what it was given — a code generator
41//! that meets something it cannot write for otherwise emits code that fails to check somewhere
42//! else, with a message about lines the reader never wrote.
43//!
44//! # The two phases
45//!
46//! An ordinary `macro` is expanded here, before anything has been checked. A `typed macro` is left
47//! exactly as written by this pass and expanded by the **checker**, because its body asks what its
48//! arguments were inferred to be; [`typed`] is that half, and it is the same interpreter with one
49//! more name in scope.
50
51use std::collections::HashMap;
52use std::sync::Arc;
53
54use beck_diag::depth::Nesting;
55use beck_diag::{Diagnostic, Diagnostics, Span};
56use beck_syntax::{sym, Lit, Node, Scope, Symbol};
57
58pub mod interp;
59pub mod typed;
60mod ui;
61pub mod vocabulary;
62
63pub use interp::{Val, BUILTINS, MAX_STEPS, RESTRICTED};
64pub use typed::{DeclInfo, Fields, TyKind, TyRepr, TypeEnv, TypedExpander, Variants};
65pub use ui::expand_ui;
66
67/// How deep a macro may expand before the expander decides it is not going to terminate.
68///
69/// This counts *expansions* — a macro whose output is another call to itself — and nothing else.
70/// Walking into a form's arguments is not an expansion, and counting it here is what used to make
71/// a 65-level-deep expression with no macros in it report that macro expansion had not terminated.
72/// The structural walk is bounded by [`Nesting`] against the ceiling the whole front end shares.
73pub const MAX_DEPTH: u32 = 64;
74
75/// How many nodes a module's macros may **produce**, in total.
76///
77/// `MAX_DEPTH` and [`Nesting`] bound how deep expansion goes and neither bounds how much it makes,
78/// which is [`14`](../../../../docs/14-review-findings.md)'s F17: *a macro that doubles its output
79/// at each of a few levels is shallow, terminating, and enormous*. Eight nestings of a two-line
80/// macro is 256 copies of its argument; sixty nestings is 10^18 of them, which is more nodes than a
81/// machine has bytes — and every one of those programs is six lines long and passes every other
82/// limit the front end has.
83///
84/// So the meter is what expansion **produces**, charged per node and shared by the whole module —
85/// per module because that is what a compile is, and because a per-call budget would let a program
86/// spend it as many times as it has calls
87/// ([`docs/82`](../../../../docs/82-the-edge-report.md) §82.5 is the same
88/// arithmetic one subsystem over).
89///
90/// The number is measured rather than declared. Across every program in this repository — the
91/// corpus, both benchmark suites, both SICP chapters, the examples and the standard library — the
92/// **largest total expansion is 138 nodes** (`sicp/ch3.beck`; `examples/todo.beck`'s page is 94), so
93/// a hundred thousand is about 725× the biggest real one. It is also about seventeen nestings of a
94/// doubling macro, which is a few dozen characters more source than a program that compiles — and
95/// that is the room a limit wants when what it separates is *legitimate* from *absurd* rather than
96/// big from small.
97///
98/// `macro_bomb.rs` is the gate, in both directions: the tree still compiles, and a doubling macro is
99/// refused.
100pub const MAX_EXPANSION: u64 = 100_000;
101
102/// The expansion budget's refusal, in one place because two callers report it: the meter itself,
103/// and a checker whose probe threw the first report away ([`typed::TypedExpander::exhausted`]).
104pub(crate) fn too_much(span: Span) -> Diagnostic {
105    Diagnostic::error("B0214", "macro expansion produced too much", span)
106        .with_primary_label("the expander stopped here")
107        .with_note(format!(
108            "the budget is {MAX_EXPANSION} nodes for the whole module, and expansion is bounded by \
109             what it *produces* rather than by how deep it goes: a macro that doubles its output is \
110             shallow, terminates, and is enormous"
111        ))
112}
113
114#[derive(Clone, Debug)]
115struct MacroDef {
116    name: Arc<str>,
117    params: Vec<Arc<str>>,
118    /// The `do` block of the macro's body.
119    body: Node,
120    span: Span,
121    /// `typed macro` — expanded by the checker rather than here (§2.4, [`typed`]). Collected in
122    /// the same table as an untyped one so that the flat namespace is one namespace: two macros of
123    /// a name collide whichever kind they are, and `B0200` is what says so.
124    typed: bool,
125}
126
127pub struct Expander<'a> {
128    macros: HashMap<Arc<str>, MacroDef>,
129    /// The module's own `def`s, callable from a macro body (§2.4's "reads of the declared module
130    /// graph").
131    defs: HashMap<Arc<str>, interp::FnDef>,
132    next_scope: u32,
133    /// What is left of [`interp::MAX_STEPS`] for the whole module, and whether it ran out.
134    steps: u64,
135    steps_spent: bool,
136    /// What is left of [`MAX_EXPANSION`], in nodes, for the whole module.
137    fuel: u64,
138    /// Whether the budget ran out, so the diagnostic is reported once rather than at every call
139    /// that would have expanded afterwards.
140    spent: bool,
141    /// How deep into the tree this walk is. Separate from the expansion depth above, because they
142    /// bound different things and only one of them means a macro is misbehaving.
143    nesting: Nesting,
144    /// What the checker inferred about the call being expanded — `Some` only while a
145    /// [`typed::TypedExpander`] is driving, which is the whole difference between the two phases.
146    types: Option<&'a TypeEnv>,
147    diags: &'a mut Diagnostics,
148}
149
150/// Expand every macro in a module to a fixpoint.
151pub fn expand_module(module: &Node, diags: &mut Diagnostics) -> Node {
152    expand_module_measured(module, diags).0
153}
154
155/// The same, with the macros of the modules this one **imports** in scope.
156///
157/// [`docs/02`](../../../../docs/02-syntax.md) §2.4: a macro is a declaration like any other, and
158/// a module that imports another gets its declarations. Until this existed a macro was usable in
159/// the file that declared it and nowhere else — not refused, simply absent — which is what kept
160/// §2.4's `derive` and §2.5's `sql"…"` out of `lib/` and made every macro an example rather than a
161/// facility.
162///
163/// `imported` are the **parsed** modules, in any order: a macro body is compile-time callable as it
164/// was *written*, before expansion, so what a macro needs from
165/// another module is its source and not its interface. That is also the limit — an import that is
166/// an interface and no implementation publishes signatures, and a macro has none.
167///
168/// Names are merged flat, which is the language's own model rather than a shortcut here: Beck links
169/// modules into one namespace with no qualified reference (`B0601`), so a macro imported from one
170/// module and a macro declared in this one collide exactly as two `def`s of one name do, and
171/// `B0200` is what says so.
172pub fn expand_module_with(module: &Node, imported: &[&Node], diags: &mut Diagnostics) -> Node {
173    expand_module_inner(module, imported, diags).0
174}
175
176/// Charge a tree of nodes against a module's expansion budget, and say whether it fitted.
177///
178/// **Iterative**, with its own stack, for the reason the walk counts at all: the tree being
179/// measured is one a macro just built, so a recursive count would be a claim about the host's
180/// stack rather than about the program
181/// ([`93`](../../../../docs/93-the-native-backends-report.md) §93.9 is the same defect one
182/// subsystem over). It also stops the moment the budget does, so the *accounting* is bounded by
183/// the budget it is accounting for — a macro that produced a billion nodes is refused after a
184/// hundred thousand of them have been counted, not after a billion.
185///
186/// Free-standing because two callers spend the same meter: the expander that has just built the
187/// tree, and [`typed::TypedExpander::charge_expansion`], which is handed one it built earlier and
188/// is putting in the program a second time.
189pub(crate) fn charge_nodes(
190    fuel: &mut u64,
191    spent: &mut bool,
192    out: &Node,
193    span: Span,
194    diags: &mut Diagnostics,
195) -> bool {
196    let mut stack = vec![out];
197    while let Some(node) = stack.pop() {
198        if *fuel == 0 {
199            if !*spent {
200                *spent = true;
201                diags.push(too_much(span));
202            }
203            return false;
204        }
205        *fuel -= 1;
206        stack.extend(node.args.iter());
207    }
208    true
209}
210
211/// Expand a module, and say how much of the interpreter's step budget is left.
212///
213/// The second half is what makes [`interp::MAX_STEPS`]'s doc comment a measurement rather than an
214/// assertion: `macro_interp.rs` expands the most expensive macro body here and prints what it
215/// cost. Nothing in the compiler reads it.
216pub fn expand_module_measured(module: &Node, diags: &mut Diagnostics) -> (Node, u64) {
217    expand_module_inner(module, &[], diags)
218}
219
220fn expand_module_inner(module: &Node, imported: &[&Node], diags: &mut Diagnostics) -> (Node, u64) {
221    let mut ex = Expander::collecting(diags);
222    // The imports first, so a macro this module declares shadows nothing silently: `B0200` fires on
223    // the second definition of a name, and the second one is this module's.
224    //
225    // "Are there macros here at all" is asked of **every** module in play, this one included. Asked
226    // of the imports alone it made whether a macro body could call an imported `def` depend on
227    // whether that other module happened to declare a macro of its own — so adding an unused macro
228    // to the imported file was the difference between `B0208` and a compile, and `B0208` states a
229    // rule ("a `def` in this module") that was not the one being applied.
230    let brings_macros = declares_a_macro(module) || imported.iter().any(|m| declares_a_macro(m));
231    for m in imported {
232        ex.collect_macros_from(m, brings_macros);
233    }
234    ex.collect_macros_from(module, brings_macros);
235
236    let mut items = Vec::with_capacity(module.args.len());
237    for (i, item) in module.args.iter().enumerate() {
238        // args[0] is the module name.
239        if i == 0 {
240            items.push(item.clone());
241            continue;
242        }
243        // A `macro` definition is consumed by the expander and does not survive into the program.
244        if item.is_form(sym::MACRO) {
245            continue;
246        }
247        // A `typed macro` survives this phase untouched, and is consumed by the checker. Its body
248        // is a template like any macro's, so expanding *into* it would expand code that has not
249        // been called yet.
250        if item.is_form(sym::TYPED_MACRO) {
251            items.push(item.clone());
252            continue;
253        }
254        let expanded = ex.expand(item, 0);
255        // `splice([…])` at the top of a module is several items where one was written — §2.4's
256        // `derive` returns the definition it decorated *and* the impls it generated.
257        //
258        // Flattened all the way down rather than one level: `derive` is handed a **block**, which
259        // is already a `do`, and returns it beside what it generated — so the answer is a `do`
260        // holding a `do`, and stopping at the first would leave a block where an item belongs.
261        flatten_into(&expanded, &mut items);
262    }
263    let steps_left = ex.steps;
264    (
265        Node::form_sym(
266            module
267                .head_sym()
268                .cloned()
269                .unwrap_or_else(|| Symbol::new(sym::MODULE)),
270            items,
271            module.span(),
272        ),
273        steps_left,
274    )
275}
276
277/// Whether a parsed module declares a **typed** macro — the question the checker asks before
278/// collecting anything, since only a typed macro is its to expand.
279pub(crate) fn declares_a_typed_macro(module: &Node) -> bool {
280    module.args.iter().any(|i| i.is_form(sym::TYPED_MACRO))
281}
282
283/// Whether a parsed module declares a macro of either kind — the one question worth asking before
284/// copying anything out of it.
285pub(crate) fn declares_a_macro(module: &Node) -> bool {
286    module
287        .args
288        .iter()
289        .any(|i| i.is_form(sym::MACRO) || i.is_form(sym::TYPED_MACRO))
290}
291
292/// Every item a macro's answer stands for, with the `do`s it is wrapped in taken off.
293///
294/// One `do` is `splice([…])`; two is `splice([do, impl])` where `do` is the block the macro was
295/// given, which is what §2.4's `derive` returns. Neither is a construct a program wrote at module
296/// level, so both are unwrapped, and a `do` that a *program* wrote there was already refused as an
297/// unsupported top-level item.
298fn flatten_into(node: &Node, out: &mut Vec<Node>) {
299    match node.is_form(sym::DO) {
300        true => node.args.iter().for_each(|a| flatten_into(a, out)),
301        false => out.push(node.clone()),
302    }
303}
304
305impl<'a> Expander<'a> {
306    /// An expander with nothing collected and the module's budgets full.
307    pub(crate) fn collecting(diags: &'a mut Diagnostics) -> Expander<'a> {
308        Expander {
309            macros: HashMap::new(),
310            defs: HashMap::new(),
311            next_scope: 1,
312            steps: interp::MAX_STEPS,
313            steps_spent: false,
314            fuel: MAX_EXPANSION,
315            spent: false,
316            nesting: Nesting::new(),
317            types: None,
318            diags,
319        }
320    }
321
322    /// The macros and compile-time-callable `def`s of one module.
323    ///
324    /// `elsewhere` says whether some *other* module in scope declares a macro. A module with no
325    /// macros of its own pays nothing for the interpreter — collecting the `def`s copies a body
326    /// each, which is proportional to the whole module, and the overwhelming majority of modules
327    /// have nothing that could ever call one. That guard has to widen by exactly one word once
328    /// macros are importable: a module with no macros that *imports* one still has to hand over its
329    /// definitions, because the imported macro's body may call them.
330    pub(crate) fn collect_macros_from(&mut self, module: &Node, elsewhere: bool) {
331        let has_macros = elsewhere || declares_a_macro(module);
332
333        for item in &module.args {
334            // A `def` is callable from a macro body, as the definition was *written*: expansion
335            // has not run yet, so a `def` whose body calls a macro is not compile-time callable.
336            // The alternative would be an expansion order that depends on who calls what.
337            if has_macros
338                && item.is_form(sym::DEF)
339                && item.args.len() >= 6
340                && item.args[2].is_form(sym::PARAMS)
341            {
342                if let (Some(name), Some(body)) = (item.args[0].as_var(), item.args.last()) {
343                    self.defs.insert(
344                        name.name.clone(),
345                        interp::FnDef {
346                            params: interp::param_names(&item.args[2]),
347                            body: body.clone(),
348                            span: item.span(),
349                        },
350                    );
351                }
352                continue;
353            }
354            let typed = item.is_form(sym::TYPED_MACRO);
355            if !(item.is_form(sym::MACRO) || typed) || item.args.len() < 3 {
356                continue;
357            }
358            let Some(name) = item.args[0].as_var() else {
359                continue;
360            };
361            let params: Vec<Arc<str>> = item.args[1]
362                .args
363                .iter()
364                .filter_map(|p| {
365                    let target = if p.is_form(sym::ANNOT) { &p.args[0] } else { p };
366                    target.as_var().map(|s| s.name.clone())
367                })
368                .collect();
369            let def = MacroDef {
370                name: name.name.clone(),
371                params,
372                body: item.args[2].clone(),
373                span: item.span(),
374                typed,
375            };
376            if self.macros.insert(name.name.clone(), def).is_some() {
377                self.diags.push(
378                    Diagnostic::error(
379                        "B0200",
380                        format!("macro `{name}` is defined twice"),
381                        item.span(),
382                    )
383                    .with_primary_label("a later definition would silently win"),
384                );
385            }
386        }
387    }
388
389    /// A scope no other expansion has, **in either phase**.
390    ///
391    /// Two expanders run over one module — this one before the checker and
392    /// [`typed::TypedExpander`] inside it — and a scope both of them minted would make a binding one
393    /// introduced visible to a reference the other introduced. That is hygiene failing, and it is
394    /// the one way it can fail *silently*, so the two are kept apart by **parity** rather than by an
395    /// argument about how many scopes either can spend: this one counts the odd numbers and the
396    /// typed expander counts the even ones. A bound would have had to hold for expansions that
397    /// mint a scope and then *fail* — an arity error mints one and charges nothing — and the number
398    /// of those is bounded only by how many macro calls a source file can hold.
399    fn fresh_scope(&mut self) -> Scope {
400        let s = Scope(self.next_scope);
401        self.next_scope += 2;
402        s
403    }
404
405    /// Charge what one expansion produced against the module's budget.
406    ///
407    /// **Iterative**, with its own stack, for the reason the walk counts at all: the tree being
408    /// measured is one a macro just built, so a recursive count would be a claim about the host's
409    /// stack rather than about the program
410    /// ([`93`](../../../../docs/93-the-native-backends-report.md) §93.9 is the same defect one
411    /// subsystem over). It also stops the moment the budget does, so the *accounting* is bounded by
412    /// the budget it is accounting for — a macro that produced a billion nodes is refused after a
413    /// hundred thousand of them have been counted, not after a billion.
414    pub(crate) fn charge(&mut self, out: &Node, span: Span) -> bool {
415        charge_nodes(&mut self.fuel, &mut self.spent, out, span, self.diags)
416    }
417
418    /// Expand a node bottom-up, then re-expand if the node itself was a macro call.
419    pub(crate) fn expand(&mut self, n: &Node, depth: u32) -> Node {
420        // Once the budget is gone nothing else is expanded: the module is not going to compile, and
421        // carrying on would be spending the rest of the compile on a program already refused.
422        if self.spent {
423            return n.clone();
424        }
425        if depth > MAX_DEPTH {
426            self.diags.push(
427                Diagnostic::error("B0201", "macro expansion did not terminate", n.span())
428                    .with_primary_label("expanded past the depth limit")
429                    .with_note(format!("the limit is {MAX_DEPTH} nested expansions")),
430            );
431            return n.clone();
432        }
433
434        // A `quote` is data: its contents are a template, not code to expand. Unquotes inside it
435        // *are* code, and are substituted by `instantiate` when the template is used.
436        if n.is_form(sym::QUOTE) {
437            return n.clone();
438        }
439
440        if !self.nesting.enter() {
441            if self.nesting.should_report() {
442                let note = self.nesting.note();
443                self.diags.push(
444                    Diagnostic::error("B0213", "the form nests too deep to expand", n.span())
445                        .with_primary_label("the expander gave up here")
446                        .with_note(note),
447                );
448            }
449            return n.clone();
450        }
451        let expanded_args: Vec<Node> = n.args.iter().map(|a| self.expand(a, depth)).collect();
452        self.nesting.leave();
453        let here = Node {
454            head: n.head.clone(),
455            args: expanded_args,
456            applied: n.applied,
457            meta: n.meta.clone(),
458        };
459
460        // Compiler-provided macros. `ui` is one because its expansion is a *recursive rewrite of a
461        // block's structure*, which template macros cannot express (§2.4's typed macros and a
462        // compile-time interpreter are what generalise this, in Phase 2).
463        if here.is_form(sym::UI) {
464            // The same four-part dance as a user macro: add a fresh scope to the input, expand,
465            // flip. Without the *add*, the flip would scope the user's own identifiers — `todos`
466            // inside a `for` would stop referring to the `todos` the caller wrote.
467            let s = self.fresh_scope();
468            let out = ui::expand_ui(&here.add_scope(s), self.diags);
469            if !self.charge(&out, here.span()) {
470                return here;
471            }
472            return self.expand(&out.flip_scope(s), depth + 1);
473        }
474
475        if !here.applied {
476            return here;
477        }
478        let Some(name) = here.head_name().map(|s| s.to_string()) else {
479            return here;
480        };
481        let Some(def) = self.macros.get(name.as_str()).cloned() else {
482            return here;
483        };
484        // A typed macro is the checker's to expand: its body asks what its arguments were inferred
485        // to be, and nothing has been inferred yet. Left exactly as written, so the call site the
486        // checker reports against is the one somebody typed.
487        if def.typed {
488            return here;
489        }
490
491        match self.apply_macro(&def, &here) {
492            Some(out) if self.charge(&out, here.span()) => self.expand(&out, depth + 1),
493            // The macro was found and did not produce code — it refused, ran out of budget, or was
494            // called wrongly, and each of those has already been reported. Leaving the call here
495            // would have the checker report a *second* thing, that it cannot find the name.
496            _ => Node::form(sym::REFUSED, Vec::new(), here.span()),
497        }
498    }
499
500    /// One expansion step: the four-part dance described at the top of this module.
501    pub(crate) fn apply_macro(&mut self, def: &MacroDef, call: &Node) -> Option<Node> {
502        let scope = self.fresh_scope();
503
504        // Keyword arguments bind by name — which is how the block rule's `do=` reaches a macro
505        // parameter called `do` (§2.3).
506        let mut positional: Vec<Node> = Vec::new();
507        let mut named: HashMap<Arc<str>, Node> = HashMap::new();
508        for a in &call.args {
509            if a.is_form(sym::KW_ARG) && a.args.len() == 2 {
510                if let Some(k) = a.args[0].as_var() {
511                    named.insert(k.name.clone(), unquote_arg(&a.args[1]).add_scope(scope));
512                    continue;
513                }
514            }
515            positional.push(unquote_arg(a).add_scope(scope));
516        }
517
518        let mut env: HashMap<Arc<str>, Val> = HashMap::new();
519        let mut pos = positional.into_iter();
520        for p in &def.params {
521            let bound = named.remove(p).or_else(|| pos.next());
522            match bound {
523                Some(v) => {
524                    env.insert(p.clone(), Val::Syntax(v));
525                }
526                None => {
527                    self.diags.push(
528                        Diagnostic::error(
529                            "B0202",
530                            format!("macro `{}` expects an argument for `{p}`", def.name),
531                            call.span(),
532                        )
533                        .with_label(def.span, "defined here"),
534                    );
535                    return None;
536                }
537            }
538        }
539        if pos.next().is_some() || !named.is_empty() {
540            self.diags.push(
541                Diagnostic::error(
542                    "B0203",
543                    format!("too many arguments for macro `{}`", def.name),
544                    call.span(),
545                )
546                .with_label(def.span, "defined here"),
547            );
548            return None;
549        }
550
551        let out = self.macro_result(def, env, call.span())?;
552        // The flip: call-site identifiers lose the scope they gained, template identifiers gain it.
553        Some(
554            out.flip_scope(scope)
555                .with_expansion(def.name.clone(), call.span()),
556        )
557    }
558
559    /// Run the macro body, and take the syntax it returned.
560    ///
561    /// The body is ordinary Beck ([`interp`]); the module's step budget is threaded through here
562    /// rather than owned by the interpreter, because a module compiles once and its macros share
563    /// what that compile is allowed to cost.
564    fn macro_result(
565        &mut self,
566        def: &MacroDef,
567        env: HashMap<Arc<str>, Val>,
568        call: Span,
569    ) -> Option<Node> {
570        let mut interp =
571            interp::Interp::new(&self.defs, &mut *self.diags, self.steps, self.steps_spent)
572                .knowing(self.types)
573                .called_at(call);
574        let out = interp.run_body(&def.name, &def.body, env, def.span);
575        self.steps = interp.steps;
576        self.steps_spent = interp.exhausted;
577        out
578    }
579}
580
581/// Strip the `quote` the block rule wraps a body in before handing it to a macro.
582///
583/// §2.3: "If the callee is a macro, it receives the AST. If it is a function, it receives a
584/// thunk." `f(x):` desugars to `f(x, do=quote(block))`, so the quote is the *marker* that this
585/// argument is syntax — a macro parameter should be bound to the block itself, not to a `quote`
586/// node it would then have to unwrap by hand.
587fn unquote_arg(n: &Node) -> Node {
588    if n.is_form(sym::QUOTE) && n.args.len() == 1 {
589        return n.args[0].clone();
590    }
591    n.clone()
592}
593
594/// Build a string literal node — shared with the `ui` builtin.
595pub(crate) fn str_lit(s: impl AsRef<str>, span: Span) -> Node {
596    Node::lit(Lit::Str(s.as_ref().into()), span)
597}
598
599#[cfg(test)]
600mod tests {
601    use super::*;
602    use beck_diag::SourceMap;
603    use beck_syntax::{parser, print};
604
605    fn expand(src: &str) -> (String, Diagnostics, SourceMap) {
606        let mut map = SourceMap::new();
607        let f = map.add("t.beck", src);
608        let mut d = Diagnostics::new();
609        let module = parser::parse_module(f, "t", src, &mut d);
610        assert!(!d.has_errors(), "parse: {}", d.render(&map));
611        let out = expand_module(&module, &mut d);
612        (print::to_sexpr(&out), d, map)
613    }
614
615    #[test]
616    fn a_template_macro_expands() {
617        let (out, d, map) = expand(
618            "macro unless(cond, do):\n\
619             \x20   return quote:\n\
620             \x20       if not $cond:\n\
621             \x20           $do\n\
622             \n\
623             def f() -> Int:\n\
624             \x20   unless(ready):\n\
625             \x20       wait()\n",
626        );
627        assert!(!d.has_errors(), "{}", d.render(&map));
628        assert!(
629            crate::ui::tests_strip(&out).contains("(if (not ready)"),
630            "{out}"
631        );
632        assert!(out.contains("(wait)"), "{out}");
633        // The macro definition itself does not survive into the program.
634        assert!(!out.contains("(macro"), "{out}");
635    }
636
637    #[test]
638    fn hygiene_prevents_a_macro_binding_from_capturing_user_code() {
639        // The macro introduces `tmp`; the caller's block also mentions `tmp`. The two must not be
640        // the same binding — the caller's `tmp` refers to whatever `tmp` meant at the call site.
641        let (out, d, map) = expand(
642            "macro twice(do):\n\
643             \x20   return quote:\n\
644             \x20       tmp = 1\n\
645             \x20       $do\n\
646             \n\
647             def f() -> Int:\n\
648             \x20   tmp = 99\n\
649             \x20   twice():\n\
650             \x20       return tmp\n",
651        );
652        assert!(!d.has_errors(), "{}", d.render(&map));
653        // The macro-introduced binder carries a scope; the user's reference does not. Core form
654        // heads are identifiers too and are scoped along with everything else, exactly as in
655        // Racket — harmless, because forms are matched by name.
656        assert!(
657            out.contains("(let{1} tmp{1} 1)"),
658            "macro binder should be scoped: {out}"
659        );
660        assert!(
661            out.contains("(let tmp 99)"),
662            "the user's own binding must stay unscoped: {out}"
663        );
664        assert!(
665            out.contains("(return tmp)"),
666            "the user's reference must stay unscoped: {out}"
667        );
668    }
669
670    #[test]
671    fn call_site_identifiers_come_back_to_their_own_scopes() {
672        // Everything the caller passed in must print exactly as written: the scope added on the
673        // way in is removed by the flip on the way out.
674        let (out, d, _) = expand(
675            "macro id(x):\n\
676             \x20   return quote:\n\
677             \x20       $x\n\
678             \n\
679             def f() -> Int:\n\
680             \x20   return id(hello)\n",
681        );
682        assert!(!d.has_errors());
683        assert!(
684            crate::ui::tests_strip(&out).contains("(return hello)"),
685            "{out}"
686        );
687    }
688
689    #[test]
690    fn splicing_inlines_a_list() {
691        let (out, d, map) = expand(
692            "macro all(items):\n\
693             \x20   return quote:\n\
694             \x20       total($*items)\n\
695             \n\
696             def f() -> Int:\n\
697             \x20   return all([1, 2, 3])\n",
698        );
699        assert!(!d.has_errors(), "{}", d.render(&map));
700        assert!(
701            crate::ui::tests_strip(&out).contains("(total 1 2 3)"),
702            "{out}"
703        );
704    }
705
706    #[test]
707    fn a_nonterminating_macro_is_reported_rather_than_hanging() {
708        let (_, d, _) = expand(
709            "macro loopy(x):\n\
710             \x20   return quote:\n\
711             \x20       loopy($x)\n\
712             \n\
713             def f() -> Int:\n\
714             \x20   return loopy(1)\n",
715        );
716        assert!(d.iter().any(|x| x.code == "B0201"));
717    }
718
719    #[test]
720    fn a_let_in_a_macro_body_computes_rather_than_substituting() {
721        // The one semantic change the interpreter made to a body that already worked: a `let`
722        // whose right-hand side is not a `quote` used to be instantiated as a *template*, so
723        // `n = 2 + 3` bound the syntax `2 + 3`. It now binds `5`, and `$n` is the literal.
724        let (out, d, map) = expand(
725            "macro five(x):\n\
726             \x20   n = 2 + 3\n\
727             \x20   return quote:\n\
728             \x20       $n + $x\n\
729             \n\
730             def f() -> Int:\n\
731             \x20   return five(1)\n",
732        );
733        assert!(!d.has_errors(), "{}", d.render(&map));
734        assert!(crate::ui::tests_strip(&out).contains("(+ 5 1)"), "{out}");
735    }
736
737    #[test]
738    fn arity_errors_name_the_macro_and_its_definition() {
739        let (_, d, _) = expand(
740            "macro two(a, b):\n\
741             \x20   return quote:\n\
742             \x20       pair($a, $b)\n\
743             \n\
744             def f() -> Int:\n\
745             \x20   return two(1)\n",
746        );
747        assert!(d.iter().any(|x| x.code == "B0202"));
748    }
749}