Lines
100 %
Functions
Branches
use super::*;
use crate::ast::Fraction;
fn sym(s: &str) -> Expr {
Expr::Symbol(s.to_string())
}
fn list(items: Vec<Expr>) -> Expr {
Expr::List(items)
fn num(n: i64) -> Expr {
Expr::Number(Fraction::from_integer(n))
fn pair(elem: PairElement) -> Option<WasmType> {
Some(WasmType::PairRef(elem))
#[test]
fn cons_cdr_literal_car_is_that_cell() {
let body = list(vec![sym("CONS"), num(1), sym("acc")]);
assert_eq!(pinned_param_type(&body, "acc"), pair(PairElement::I32));
fn cons_cdr_nonliteral_car_widens_to_anyref() {
let body = list(vec![sym("CONS"), sym("x"), sym("acc")]);
assert_eq!(pinned_param_type(&body, "acc"), pair(PairElement::AnyRef));
assert_eq!(pinned_param_type(&body, "x"), None);
fn no_cons_cdr_use_is_unconstrained() {
let body = list(vec![sym("*"), sym("x"), num(2)]);
fn string_native_arg_pins_stringref() {
// (string= s "x") → s is a string.
let body = list(vec![sym("STRING="), sym("s"), Expr::String("x".into())]);
assert_eq!(pinned_param_type(&body, "s"), Some(WasmType::StringRef));
fn quoted_string_native_does_not_pin() {
// (quote (string= x "a")) — quoted data must NOT pin `x` to StringRef.
let body = list(vec![
sym("quote"),
list(vec![sym("string="), sym("x"), Expr::String("a".into())]),
]);
fn quasiquoted_string_native_does_not_pin() {
sym("quasiquote"),
fn shadowing_let_body_does_not_retype_outer_param() {
// (let ((x nil)) (cons 1 x)) — cdr is the INNER `x`; outer stays None.
let inner = list(vec![
sym("LET"),
list(vec![list(vec![sym("x"), Expr::Nil])]),
list(vec![sym("CONS"), num(1), sym("x")]),
assert_eq!(pinned_param_type(&inner, "x"), None);
fn let_initializer_cons_cdr_is_still_outer_scope() {
// (let ((acc (cons 1 acc))) acc) — the init `(cons 1 acc)` references the
// OUTER `acc` (the binding isn't in scope for its own init), so `acc` IS
// a list accumulator. Scanning must reach the init even though the let
// rebinds `acc`.
let form = list(vec![
list(vec![list(vec![
sym("acc"),
list(vec![sym("CONS"), num(1), sym("acc")]),
])]),
assert_eq!(pinned_param_type(&form, "acc"), pair(PairElement::I32));
fn dolist_list_expr_cons_cdr_is_outer_scope() {
// (dolist (e (cons 1 acc)) e) — the list expr references outer `acc`.
sym("DOLIST"),
list(vec![sym("e"), list(vec![sym("CONS"), num(1), sym("acc")])]),
sym("e"),
fn lambda_binder_shadows_outer_param() {
list(vec![
sym("LAMBDA"),
list(vec![sym("x")]),
]),
sym("y"),
fn short_binder_body_does_not_panic_on_slice() {
// Regression (AFL): a too-short binder form — `(do)`, `(let)`,
// `(lambda)` — used to index `&elems[2..]` / `&elems[1..]` out of range
// and panic. Param inference is best-effort, so a malformed form must
// contribute no pin (return None), never crash.
for head in ["DO", "DO*", "LET", "LET*", "DOLIST", "LAMBDA"] {
let bare = list(vec![sym(head)]);
assert_eq!(pinned_param_type(&bare, "x"), None, "bare ({head})");
// Nested under a lambda body, mirroring the fuzz-found shape
// `(lambda (s) (do))`.
let nested = list(vec![sym("DO")]);
assert_eq!(pinned_param_type(&nested, "s"), None);
fn literal_car_cells_map_to_their_slots() {
assert_eq!(car_cell_element(&num(3)), PairElement::I32);
assert_eq!(car_cell_element(&num(1 << 30)), PairElement::Ratio);
assert_eq!(car_cell_element(&Expr::Nil), PairElement::Bool);
assert_eq!(car_cell_element(&Expr::T), PairElement::Bool);
assert_eq!(
car_cell_element(&Expr::String("s".into())),
PairElement::StringRef
);
assert_eq!(car_cell_element(&sym("v")), PairElement::AnyRef);