Lines
100 %
Functions
Branches
use super::*;
use crate::ast::{LambdaParams, WasmType};
use crate::runtime::{Symbol, SymbolKind};
fn runtime_symbols(names: &[&str]) -> SymbolTable {
// Reader case-folds source symbols to uppercase; matching that
// here means the SymbolTable lookup hits when the test body
// parses `x` as `X`.
let mut table = SymbolTable::new();
for n in names {
table.define(
Symbol::new(n.to_uppercase(), SymbolKind::Variable)
.with_value(Expr::WasmRuntime(WasmType::I32)),
);
}
table
fn no_captures() -> CaptureSet {
CaptureSet::default()
fn parse(src: &str) -> Expr {
crate::Reader::parse_expr(src).expect("parse")
#[test]
fn empty_body_has_no_captures() {
let symbols = runtime_symbols(&["x"]);
let params = LambdaParams::simple(vec!["a".into()]);
let body = Expr::Nil;
assert_eq!(compute_captures(&symbols, ¶ms, &body), no_captures());
fn body_referencing_only_params_has_no_captures() {
let body = parse("(+ a 1)");
fn body_referencing_outer_runtime_name_captures_it() {
let body = parse("(+ a x)");
let captures = compute_captures(&symbols, ¶ms, &body);
assert_eq!(captures.len(), 1);
assert!(captures.contains("X"));
fn multiple_captures_preserve_first_seen_order() {
let symbols = runtime_symbols(&["x", "y", "z"]);
let params = LambdaParams::simple(vec![]);
let body = parse("(+ z x y x)");
let collected: Vec<&str> = captures.iter().collect();
assert_eq!(collected, vec!["Z", "X", "Y"]);
fn constant_globals_are_not_captured() {
// A symbol whose value is not WasmRuntime/WasmLocal — e.g. a
// builtin or constant — must not be flagged as a capture.
let mut symbols = SymbolTable::new();
symbols.define(
Symbol::new("PI", SymbolKind::Variable)
.with_value(Expr::Number(num_rational::Ratio::new(22, 7))),
let body = parse("(+ a pi)");
fn unknown_symbols_are_not_captured() {
// Symbol not in the table at all — capture analysis can't
// claim it; later compile passes will surface UndefinedSymbol.
let symbols = runtime_symbols(&[]);
let body = parse("(+ undefined-thing 1)");
fn nested_let_shadows_outer_capture() {
let body = parse("(let ((x 5)) (+ x 1))");
fn nested_let_init_still_captures_outer() {
// The initializer of a LET binding sees the outer scope, so
// `x` in `(let ((x x)) ...)` is a capture of the outer x.
let body = parse("(let ((x x)) (+ x 1))");
fn nested_lambda_introduces_own_params_but_outer_capture_propagates() {
let body = parse("(lambda (a) (+ a x))");
fn nested_lambda_param_does_not_become_outer_capture() {
let body = parse("(lambda (a) (+ a 1))");
fn quoted_symbols_are_not_captures() {
let body = parse("'x");
fn dolist_var_does_not_become_capture() {
let symbols = runtime_symbols(&["xs"]);
let body = parse("(dolist (item xs) (+ item 1))");
assert!(captures.contains("XS"));
assert!(!captures.contains("ITEM"));