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}