1
use super::*;
2
use crate::ast::{LambdaParams, WasmType};
3
use crate::runtime::{Symbol, SymbolKind};
4

            
5
11
fn runtime_symbols(names: &[&str]) -> SymbolTable {
6
    // Reader case-folds source symbols to uppercase; matching that
7
    // here means the SymbolTable lookup hits when the test body
8
    // parses `x` as `X`.
9
11
    let mut table = SymbolTable::new();
10
11
    for n in names {
11
11
        table.define(
12
11
            Symbol::new(n.to_uppercase(), SymbolKind::Variable)
13
11
                .with_value(Expr::WasmRuntime(WasmType::I32)),
14
11
        );
15
11
    }
16
11
    table
17
11
}
18

            
19
7
fn no_captures() -> CaptureSet {
20
7
    CaptureSet::default()
21
7
}
22

            
23
11
fn parse(src: &str) -> Expr {
24
11
    crate::Reader::parse_expr(src).expect("parse")
25
11
}
26

            
27
#[test]
28
1
fn empty_body_has_no_captures() {
29
1
    let symbols = runtime_symbols(&["x"]);
30
1
    let params = LambdaParams::simple(vec!["a".into()]);
31
1
    let body = Expr::Nil;
32
1
    assert_eq!(compute_captures(&symbols, &params, &body), no_captures());
33
1
}
34

            
35
#[test]
36
1
fn body_referencing_only_params_has_no_captures() {
37
1
    let symbols = runtime_symbols(&["x"]);
38
1
    let params = LambdaParams::simple(vec!["a".into()]);
39
1
    let body = parse("(+ a 1)");
40
1
    assert_eq!(compute_captures(&symbols, &params, &body), no_captures());
41
1
}
42

            
43
#[test]
44
1
fn body_referencing_outer_runtime_name_captures_it() {
45
1
    let symbols = runtime_symbols(&["x"]);
46
1
    let params = LambdaParams::simple(vec!["a".into()]);
47
1
    let body = parse("(+ a x)");
48
1
    let captures = compute_captures(&symbols, &params, &body);
49
1
    assert_eq!(captures.len(), 1);
50
1
    assert!(captures.contains("X"));
51
1
}
52

            
53
#[test]
54
1
fn multiple_captures_preserve_first_seen_order() {
55
1
    let symbols = runtime_symbols(&["x", "y", "z"]);
56
1
    let params = LambdaParams::simple(vec![]);
57
1
    let body = parse("(+ z x y x)");
58
1
    let captures = compute_captures(&symbols, &params, &body);
59
1
    let collected: Vec<&str> = captures.iter().collect();
60
1
    assert_eq!(collected, vec!["Z", "X", "Y"]);
61
1
}
62

            
63
#[test]
64
1
fn constant_globals_are_not_captured() {
65
    // A symbol whose value is not WasmRuntime/WasmLocal — e.g. a
66
    // builtin or constant — must not be flagged as a capture.
67
1
    let mut symbols = SymbolTable::new();
68
1
    symbols.define(
69
1
        Symbol::new("PI", SymbolKind::Variable)
70
1
            .with_value(Expr::Number(num_rational::Ratio::new(22, 7))),
71
    );
72
1
    let params = LambdaParams::simple(vec!["a".into()]);
73
1
    let body = parse("(+ a pi)");
74
1
    assert_eq!(compute_captures(&symbols, &params, &body), no_captures());
75
1
}
76

            
77
#[test]
78
1
fn unknown_symbols_are_not_captured() {
79
    // Symbol not in the table at all — capture analysis can't
80
    // claim it; later compile passes will surface UndefinedSymbol.
81
1
    let symbols = runtime_symbols(&[]);
82
1
    let params = LambdaParams::simple(vec![]);
83
1
    let body = parse("(+ undefined-thing 1)");
84
1
    assert_eq!(compute_captures(&symbols, &params, &body), no_captures());
85
1
}
86

            
87
#[test]
88
1
fn nested_let_shadows_outer_capture() {
89
1
    let symbols = runtime_symbols(&["x"]);
90
1
    let params = LambdaParams::simple(vec![]);
91
1
    let body = parse("(let ((x 5)) (+ x 1))");
92
1
    assert_eq!(compute_captures(&symbols, &params, &body), no_captures());
93
1
}
94

            
95
#[test]
96
1
fn nested_let_init_still_captures_outer() {
97
    // The initializer of a LET binding sees the outer scope, so
98
    // `x` in `(let ((x x)) ...)` is a capture of the outer x.
99
1
    let symbols = runtime_symbols(&["x"]);
100
1
    let params = LambdaParams::simple(vec![]);
101
1
    let body = parse("(let ((x x)) (+ x 1))");
102
1
    let captures = compute_captures(&symbols, &params, &body);
103
1
    assert_eq!(captures.len(), 1);
104
1
    assert!(captures.contains("X"));
105
1
}
106

            
107
#[test]
108
1
fn nested_lambda_introduces_own_params_but_outer_capture_propagates() {
109
1
    let symbols = runtime_symbols(&["x"]);
110
1
    let params = LambdaParams::simple(vec![]);
111
1
    let body = parse("(lambda (a) (+ a x))");
112
1
    let captures = compute_captures(&symbols, &params, &body);
113
1
    assert_eq!(captures.len(), 1);
114
1
    assert!(captures.contains("X"));
115
1
}
116

            
117
#[test]
118
1
fn nested_lambda_param_does_not_become_outer_capture() {
119
1
    let symbols = runtime_symbols(&[]);
120
1
    let params = LambdaParams::simple(vec![]);
121
1
    let body = parse("(lambda (a) (+ a 1))");
122
1
    assert_eq!(compute_captures(&symbols, &params, &body), no_captures());
123
1
}
124

            
125
#[test]
126
1
fn quoted_symbols_are_not_captures() {
127
1
    let symbols = runtime_symbols(&["x"]);
128
1
    let params = LambdaParams::simple(vec![]);
129
1
    let body = parse("'x");
130
1
    assert_eq!(compute_captures(&symbols, &params, &body), no_captures());
131
1
}
132

            
133
#[test]
134
1
fn dolist_var_does_not_become_capture() {
135
1
    let symbols = runtime_symbols(&["xs"]);
136
1
    let params = LambdaParams::simple(vec![]);
137
1
    let body = parse("(dolist (item xs) (+ item 1))");
138
1
    let captures = compute_captures(&symbols, &params, &body);
139
1
    assert_eq!(captures.len(), 1);
140
1
    assert!(captures.contains("XS"));
141
1
    assert!(!captures.contains("ITEM"));
142
1
}