1
//! DEFMACRO and macro expansion codegen. Macros are expanded at
2
//! compile time by `expand_macro` (in expr.rs) and the expansion is
3
//! re-dispatched into `compile_for_effect` / `compile_for_stack`.
4

            
5
use super::common::{compile_and_validate, compile_expect_error};
6

            
7
#[test]
8
1
fn defmacro_and_simple_expansion() {
9
1
    compile_and_validate("(defmacro inc (x) `(+ ,x 1)) (inc 5)");
10
1
}
11

            
12
#[test]
13
1
fn defmacro_with_unquote_splicing() {
14
1
    compile_and_validate("(defmacro list-of (&rest xs) `(list ,@xs)) (list-of 1 2 3)");
15
1
}
16

            
17
#[test]
18
1
fn macroexpand_1_form() {
19
1
    compile_and_validate("(defmacro inc (x) `(+ ,x 1)) (macroexpand-1 '(inc 5))");
20
1
}
21

            
22
#[test]
23
1
fn macroexpand_form() {
24
1
    compile_and_validate("(defmacro inc (x) `(+ ,x 1)) (macroexpand '(inc 5))");
25
1
}
26

            
27
#[test]
28
1
fn defmacro_missing_body_errors() {
29
1
    let err = compile_expect_error("(defmacro broken)");
30
1
    assert!(err.contains("DEFMACRO"), "got: {err}");
31
1
}
32

            
33
#[test]
34
1
fn macro_used_inside_expression() {
35
1
    compile_and_validate("(defmacro double (x) `(* ,x 2)) (+ (double 3) 1)");
36
1
}
37

            
38
#[test]
39
1
fn self_referential_macro_errors_instead_of_overflowing() {
40
    // A macro expanding to itself would recurse until the native stack
41
    // overflows; the depth guard turns it into a structured compile error.
42
1
    let err = compile_expect_error("(defmacro loopmac () '(loopmac)) (loopmac)");
43
1
    assert!(
44
1
        err.contains("macro expansion exceeded depth"),
45
        "expected a macro-depth error, got: {err}"
46
    );
47
1
}
48

            
49
#[test]
50
1
fn macro_recursing_via_macroexpand_1_errors_instead_of_overflowing() {
51
    // `macroexpand-1` is one-step, but a macro whose body re-expands itself
52
    // via `(macroexpand-1 '(self))` recurses through the expander. The
53
    // shared depth guard around `macroexpand-1`'s expansion bounds it.
54
1
    let err = compile_expect_error("(defmacro loopmac () (macroexpand-1 '(loopmac))) (loopmac)");
55
1
    assert!(
56
1
        err.contains("macro expansion exceeded depth"),
57
        "expected a macro-depth error, got: {err}"
58
    );
59
1
}
60

            
61
#[test]
62
1
fn defmacro_with_docstring_and_body() {
63
    // 3rd arg is a String → doc; body starts at arg 4.
64
1
    compile_and_validate("(defmacro inc (x) \"increment\" `(+ ,x 1))");
65
1
}
66

            
67
#[test]
68
1
fn defmacro_with_docstring_multi_body_wraps_begin() {
69
1
    compile_and_validate("(defmacro mname (x) \"doc string\" `(+ ,x 1) `(* ,x 2)) (mname 7)");
70
1
}
71

            
72
#[test]
73
1
fn defmacro_non_symbol_name_errors() {
74
1
    let err = compile_expect_error("(defmacro 42 (x) x)");
75
1
    assert!(err.contains("DEFMACRO"), "got: {err}");
76
1
}
77

            
78
#[test]
79
1
fn defmacro_with_aux_errors() {
80
1
    let err = compile_expect_error("(defmacro nope (x &aux y) x)");
81
1
    assert!(
82
1
        err.contains("&aux") || err.contains("DEFMACRO"),
83
        "got: {err}"
84
    );
85
1
}
86

            
87
#[test]
88
1
fn macroexpand_1_wrong_arity_errors() {
89
1
    let err = compile_expect_error("(macroexpand-1)");
90
1
    assert!(err.contains("MACROEXPAND-1"), "got: {err}");
91
1
}
92

            
93
#[test]
94
1
fn macroexpand_wrong_arity_errors() {
95
1
    let err = compile_expect_error("(macroexpand)");
96
1
    assert!(err.contains("MACROEXPAND"), "got: {err}");
97
1
}
98

            
99
#[test]
100
1
fn macroexpand_on_non_macro_returns_input() {
101
    // `(+ 1 2)` is not a macro — macroexpand returns the form
102
    // unchanged after one iteration.
103
1
    compile_and_validate("(macroexpand '(+ 1 2))");
104
1
}
105

            
106
#[test]
107
1
fn macroexpand_1_on_atom_returns_atom() {
108
    // Atoms are not macros; expand_1 returns them unchanged through
109
    // the Expr::Quote -> non-list fall-through arm.
110
1
    compile_and_validate("(macroexpand-1 'foo)");
111
1
}
112

            
113
#[test]
114
1
fn namespaced_macro_defines_and_expands() {
115
    // ADR-0029: a macro defined under a namespace key (`util:dbl`) expands
116
    // when invoked qualified. Macros are global-keyed like any symbol; the
117
    // body's `*` is a global builtin (unqualified), matching the unhygienic
118
    // rule that a namespaced macro's body resolves in the expansion env.
119
1
    compile_and_validate("(defmacro util:dbl (x) `(* ,x 2)) (util:dbl 21)");
120
1
}
121

            
122
#[test]
123
1
fn namespaced_macro_unqualified_call_does_not_expand() {
124
    // Strict resolution: `dbl` is NOT `util:dbl`. Calling the macro
125
    // unqualified finds no such macro/fn, so it is an undefined call.
126
1
    let err = compile_expect_error("(defmacro util:dbl (x) `(* ,x 2)) (dbl 21)");
127
1
    let lower = err.to_lowercase();
128
1
    assert!(
129
1
        lower.contains("undefined") && lower.contains("dbl"),
130
        "expected 'Undefined symbol: dbl', got: {err}"
131
    );
132
1
}
133

            
134
#[test]
135
1
fn macroexpand_recursive_chain() {
136
    // `outer` expands to `(inner ...)` which expands to `(+ ...)` —
137
    // exercises the `loop { expand; if expanded == form { break } }`
138
    // path.
139
1
    compile_and_validate(
140
1
        "(defmacro inner (x) `(+ ,x 1)) \
141
1
         (defmacro outer (x) `(inner ,x)) \
142
1
         (macroexpand '(outer 5))",
143
    );
144
1
}