Lines
100 %
Functions
Branches
//! DEFMACRO and macro expansion codegen. Macros are expanded at
//! compile time by `expand_macro` (in expr.rs) and the expansion is
//! re-dispatched into `compile_for_effect` / `compile_for_stack`.
use super::common::{compile_and_validate, compile_expect_error};
#[test]
fn defmacro_and_simple_expansion() {
compile_and_validate("(defmacro inc (x) `(+ ,x 1)) (inc 5)");
}
fn defmacro_with_unquote_splicing() {
compile_and_validate("(defmacro list-of (&rest xs) `(list ,@xs)) (list-of 1 2 3)");
fn macroexpand_1_form() {
compile_and_validate("(defmacro inc (x) `(+ ,x 1)) (macroexpand-1 '(inc 5))");
fn macroexpand_form() {
compile_and_validate("(defmacro inc (x) `(+ ,x 1)) (macroexpand '(inc 5))");
fn defmacro_missing_body_errors() {
let err = compile_expect_error("(defmacro broken)");
assert!(err.contains("DEFMACRO"), "got: {err}");
fn macro_used_inside_expression() {
compile_and_validate("(defmacro double (x) `(* ,x 2)) (+ (double 3) 1)");
fn self_referential_macro_errors_instead_of_overflowing() {
// A macro expanding to itself would recurse until the native stack
// overflows; the depth guard turns it into a structured compile error.
let err = compile_expect_error("(defmacro loopmac () '(loopmac)) (loopmac)");
assert!(
err.contains("macro expansion exceeded depth"),
"expected a macro-depth error, got: {err}"
);
fn macro_recursing_via_macroexpand_1_errors_instead_of_overflowing() {
// `macroexpand-1` is one-step, but a macro whose body re-expands itself
// via `(macroexpand-1 '(self))` recurses through the expander. The
// shared depth guard around `macroexpand-1`'s expansion bounds it.
let err = compile_expect_error("(defmacro loopmac () (macroexpand-1 '(loopmac))) (loopmac)");
fn defmacro_with_docstring_and_body() {
// 3rd arg is a String → doc; body starts at arg 4.
compile_and_validate("(defmacro inc (x) \"increment\" `(+ ,x 1))");
fn defmacro_with_docstring_multi_body_wraps_begin() {
compile_and_validate("(defmacro mname (x) \"doc string\" `(+ ,x 1) `(* ,x 2)) (mname 7)");
fn defmacro_non_symbol_name_errors() {
let err = compile_expect_error("(defmacro 42 (x) x)");
fn defmacro_with_aux_errors() {
let err = compile_expect_error("(defmacro nope (x &aux y) x)");
err.contains("&aux") || err.contains("DEFMACRO"),
"got: {err}"
fn macroexpand_1_wrong_arity_errors() {
let err = compile_expect_error("(macroexpand-1)");
assert!(err.contains("MACROEXPAND-1"), "got: {err}");
fn macroexpand_wrong_arity_errors() {
let err = compile_expect_error("(macroexpand)");
assert!(err.contains("MACROEXPAND"), "got: {err}");
fn macroexpand_on_non_macro_returns_input() {
// `(+ 1 2)` is not a macro — macroexpand returns the form
// unchanged after one iteration.
compile_and_validate("(macroexpand '(+ 1 2))");
fn macroexpand_1_on_atom_returns_atom() {
// Atoms are not macros; expand_1 returns them unchanged through
// the Expr::Quote -> non-list fall-through arm.
compile_and_validate("(macroexpand-1 'foo)");
fn namespaced_macro_defines_and_expands() {
// ADR-0029: a macro defined under a namespace key (`util:dbl`) expands
// when invoked qualified. Macros are global-keyed like any symbol; the
// body's `*` is a global builtin (unqualified), matching the unhygienic
// rule that a namespaced macro's body resolves in the expansion env.
compile_and_validate("(defmacro util:dbl (x) `(* ,x 2)) (util:dbl 21)");
fn namespaced_macro_unqualified_call_does_not_expand() {
// Strict resolution: `dbl` is NOT `util:dbl`. Calling the macro
// unqualified finds no such macro/fn, so it is an undefined call.
let err = compile_expect_error("(defmacro util:dbl (x) `(* ,x 2)) (dbl 21)");
let lower = err.to_lowercase();
lower.contains("undefined") && lower.contains("dbl"),
"expected 'Undefined symbol: dbl', got: {err}"
fn macroexpand_recursive_chain() {
// `outer` expands to `(inner ...)` which expands to `(+ ...)` —
// exercises the `loop { expand; if expanded == form { break } }`
// path.
compile_and_validate(
"(defmacro inner (x) `(+ ,x 1)) \
(defmacro outer (x) `(inner ,x)) \
(macroexpand '(outer 5))",