1
//! ADR-0029 colon-namespace resolution at the compile/eval boundary.
2
//!
3
//! Reader-level grammar is covered by `reader_tests.rs`; these tests pin the
4
//! *resolution* contract: a namespaced `defun` defines + resolves under its
5
//! canonical `NS:NAME` key, resolution is strict-lexical (no ambient current
6
//! namespace, no cross-namespace fallback), and qualified self-recursion works.
7

            
8
use super::common::{compile_and_validate, compile_expect_error, wrap_with_runtime_i32};
9

            
10
#[test]
11
1
fn namespaced_defun_defines_and_resolves() {
12
    // Define `finance:double` and call it qualified — resolves to the
13
    // canonical FINANCE:DOUBLE key.
14
1
    compile_and_validate("(defun finance:double (x) (* x 2)) (finance:double 21)");
15
1
}
16

            
17
#[test]
18
1
fn double_colon_call_resolves_to_same_binding() {
19
    // `finance::double` folds to the same FINANCE:DOUBLE key as the single
20
    // colon used to define it.
21
1
    compile_and_validate("(defun finance:double (x) (* x 2)) (finance::double 21)");
22
1
}
23

            
24
#[test]
25
1
fn qualified_self_recursion_resolves() {
26
    // A namespaced fn recurses only when it calls itself QUALIFIED — bare `f`
27
    // would resolve to a global `F`, not `NS:F`. The arg is a RUNTIME index
28
    // (`IDX`), so the call can't const-fold and must lower through the
29
    // monomorph runtime-call path, which keys recursion on the canonical name.
30
1
    compile_and_validate(&wrap_with_runtime_i32(
31
1
        "(defun ns:countdown (n) (if (= n 0) 0 (ns:countdown (- n 1)))) (ns:countdown IDX)",
32
1
    ));
33
1
}
34

            
35
#[test]
36
1
fn unqualified_body_symbol_does_not_resolve_into_namespace() {
37
    // Inside `(defun ns:f ...)`, a bare `helper` is global, NOT `ns:helper`.
38
    // Only `ns:helper` is defined, so the unqualified call is undefined.
39
1
    let err = compile_expect_error("(defun ns:helper (x) x) (defun ns:f (x) (helper x)) (ns:f 1)");
40
1
    let lower = err.to_lowercase();
41
1
    assert!(
42
1
        lower.contains("undefined") && lower.contains("helper"),
43
        "expected 'Undefined symbol: HELPER' for the bare helper, got: {err}"
44
    );
45
1
}
46

            
47
#[test]
48
1
fn bare_self_call_in_namespaced_defun_is_not_the_namespaced_fn() {
49
    // Bare `countdown` inside `(defun ns:countdown ...)` resolves to global
50
    // COUNTDOWN (undefined here), proving no implicit self-qualification.
51
1
    let err = compile_expect_error(
52
1
        "(defun ns:countdown (n) (if (= n 0) 0 (countdown (- n 1)))) (ns:countdown 3)",
53
    );
54
1
    let lower = err.to_lowercase();
55
1
    assert!(
56
1
        lower.contains("undefined") && lower.contains("countdown"),
57
        "expected 'Undefined symbol: COUNTDOWN' for the bare self-call, got: {err}"
58
    );
59
1
}
60

            
61
#[test]
62
1
fn universal_prelude_helpers_are_available() {
63
    // ADR-0029: the universal prelude (math:* / list:*) is auto-loaded into
64
    // every table, so a script can call its helpers without defining them.
65
1
    compile_and_validate("(math:square 7)");
66
1
    compile_and_validate("(list:second (list 1 2 3))");
67
1
}
68

            
69
#[test]
70
1
fn namespaced_and_global_same_base_coexist() {
71
    // A namespaced `m:inc` and a global `inc` are distinct bindings; each
72
    // resolves to its own definition.
73
1
    compile_and_validate(
74
1
        "(defun inc (x) (+ x 1)) (defun m:inc (x) (+ x 100)) (+ (inc 1) (m:inc 1))",
75
    );
76
1
}