Lines
98.2 %
Functions
100 %
Branches
//! CAR/CDR/CONS list-construction extras, plus LENGTH / APPEND / PAIR?
//! (const-fold + runtime pair-chain paths).
use super::common::{compile_and_validate, compile_expect_error, wrap_with_runtime_ratio};
#[test]
fn car_of_static_pair() {
compile_and_validate("(car (cons 1 nil))");
}
fn cdr_of_static_pair() {
compile_and_validate("(cdr (cons 1 (cons 2 nil)))");
fn list_constructor_with_constants() {
compile_and_validate("(list 1 2 3)");
fn list_with_strings() {
compile_and_validate("(list \"a\" \"b\" \"c\")");
fn list_empty_is_nil() {
compile_and_validate("(list)");
fn cons_chain_three_levels() {
compile_and_validate("(cons 1 (cons 2 (cons 3 (cons 4 nil))))");
fn car_after_cons_of_quoted_constant() {
compile_and_validate("(car '(1 2 3))");
fn cdr_after_cons_of_quoted_constant() {
compile_and_validate("(cdr '(1 2 3))");
fn car_cdr_of_quoted_constant_at_value_position() {
// Quoted-datum CAR/CDR lowers on BOTH compile surfaces: a numeric element
// folds to its value; a quoted tail or a compound/symbol head renders as a
// datum string. The eval-with-type path used to trap on the quoted tail.
compile_and_validate("(car (cdr '(1 2 3)))");
compile_and_validate("(car '((1 2) 3))");
compile_and_validate("(car '(x y))");
compile_and_validate("(cdr '(a b c))");
fn reverse_cons_of_constant_list_at_value_position() {
// Same divergence class as CAR/CDR: a constant / runtime-builder list
// through REVERSE or CONS folds to a datum that the eval-with-type stack
// handler used to reject. Both surfaces now lower it.
compile_and_validate("(reverse '(1 2 3))");
compile_and_validate("(reverse (list 1 2 3))");
compile_and_validate("(cons 0 '(1 2 3))");
compile_and_validate("(cons 0 (list 1 2 3))");
compile_and_validate("(cons 1 2)");
// All-constant APPEND folds to a datum on both surfaces (incl. a symbol
// list, which renders to its printed form rather than trapping).
compile_and_validate("(append '(1 2) '(3))");
compile_and_validate("(append '(a b) '(c))");
fn map_over_static_list() {
compile_and_validate("(defun double (x) (* x 2)) (map (function double) '(1 2 3))");
fn reverse_static_list() {
fn reverse_empty_list() {
compile_and_validate("(reverse '())");
fn map_with_let_bound_lambda_over_static_list() {
// Runtime closure dispatch on a literal list — rewrites per-element
// funcall via call_ref.
compile_and_validate("(let ((double (lambda (x) (* x 2)))) (map double '(1 2 3)))");
fn map_with_let_bound_lambda_over_runtime_pair_chain() {
// Runtime closure + runtime PairRef list — full reverse-build-then-
// reverse loop.
compile_and_validate(&wrap_with_runtime_ratio(
"(let ((double (lambda (n) (* n 2)))) \
(map double (cons X (cons (transaction-post-date 1) nil))))",
));
fn map_static_fn_over_runtime_pair_chain() {
// Static defun + runtime PairRef list — exercise the runtime-list
// path with a non-closure callee.
"(defun double (n) (* n 2)) \
(map (function double) (cons X (cons (transaction-post-date 1) nil)))",
fn reverse_runtime_pair_chain() {
// (cons X (cons (transaction-post-date 1) ...)) builds a runtime
// PairRef<ratio> chain — reverse must walk the cells via the
// pair_new accumulator loop, not constant-fold.
"(reverse (cons X (cons (transaction-post-date 1) nil)))",
fn filter_constant_fold_with_static_pred() {
compile_and_validate("(defun pos? (x) (< 0 x)) (filter (function pos?) '(1 -1 2 -2 3))");
fn filter_with_let_bound_lambda_over_static_list() {
// Runtime closure dispatch over a literal list — per-element
// FUNCALL → call_ref + truth-flag prepend loop.
compile_and_validate("(let ((pos? (lambda (x) (< 0 x)))) (filter pos? '(1 -1 2 -2 3)))");
fn filter_static_fn_over_runtime_pair_chain() {
// Static predicate + runtime PairRef list — exercises the
// runtime-list path with the predicate routed through FUNCALL.
"(defun pos? (x) (< 0 x)) \
(filter (function pos?) (cons X (cons (transaction-post-date 1) nil)))",
fn filter_with_let_bound_lambda_over_runtime_pair_chain() {
// Runtime closure + runtime PairRef — full reverse-build-then-
// reverse loop with conditional prepend.
"(let ((pos? (lambda (x) (< 0 x)))) \
(filter pos? (cons X (cons (transaction-post-date 1) nil))))",
fn fold_constant_fold_sum() {
compile_and_validate("(defun add (a b) (+ a b)) (fold (function add) 0 '(1 2 3 4))");
fn fold_with_let_bound_lambda_over_static_list() {
// Runtime closure threading the accumulator through the literal
// path's per-element FUNCALL → call_ref.
compile_and_validate(
"(let ((acc-add (lambda (acc x) (+ acc x)))) (fold acc-add 0 '(1 2 3 4)))",
);
fn fold_static_fn_over_runtime_pair_chain() {
// Static fn + runtime PairRef list — runtime-list FOLD path.
"(defun add (a b) (+ a b)) \
(fold (function add) 0 (cons X (cons (transaction-post-date 1) nil)))",
fn fold_with_let_bound_lambda_over_runtime_pair_chain() {
"(let ((acc-add (lambda (acc x) (+ acc x)))) \
(fold acc-add 0 (cons X (cons (transaction-post-date 1) nil))))",
// LENGTH / APPEND / PAIR? — previously phantom natives (registered
// symbols + documented, no codegen handler). Regression for that hole.
fn length_constant_folds() {
compile_and_validate("(length '(1 2 3))");
fn length_in_index_arithmetic() {
// ADR-0028: LENGTH is an Index (a count), so it composes with Index
// arithmetic — `(+ 1 (length …))` is Index + Index → Index.
compile_and_validate("(+ 1 (length '(1 2 3)))");
fn length_runtime_in_index_arithmetic() {
// A runtime LENGTH is a runtime Index and composes with an integer literal
// under Index arithmetic (it does not implicitly become a Scalar).
"(+ (length (cons X (cons (transaction-post-date 1) nil))) 1)",
fn length_runtime_pair_chain() {
"(length (cons X (cons (transaction-post-date 1) nil)))",
fn length_runtime_compared_to_constant() {
// The canonical use: `(= (length …) N)` on a runtime chain.
"(= (length (cons X (cons (transaction-post-date 1) nil))) 2)",
fn append_constant_folds() {
compile_and_validate("(append '(1 2) '(3 4))");
fn append_runtime_chain_with_nil() {
"(append (cons X (cons (transaction-post-date 1) nil)) nil)",
/// General concat: a runtime list followed by a NON-empty constant list. The
/// constant operand is materialized into a runtime `$pair` chain (the cell
/// type is monomorphic), so this is no longer rejected.
fn append_runtime_chain_then_constant_list() {
compile_and_validate(&wrap_with_runtime_ratio("(append (cons X nil) '(1 2))"));
/// Mirror: a constant list followed by a runtime chain.
fn append_constant_list_then_runtime_chain() {
compile_and_validate(&wrap_with_runtime_ratio("(append '(1 2) (cons X nil))"));
/// A constant string list materialized alongside a runtime chain — string
/// members lower through the cons builder as data.
fn append_constant_string_list_with_runtime_chain() {
"(append (cons (transaction-post-date 1) nil) '(7 8))",
/// Round-2 regression: a constant list whose members aren't runtime-
/// representable (symbols — no runtime value anywhere in the language) must
/// give the standard "cannot compile to WASM stack value" error, NOT the
/// misleading "Undefined symbol A" that came from re-evaluating the datum as a
/// variable reference. The members are quoted before materialization so a
/// symbol surfaces the clean error. (Full symbol-list support is a separate
/// slice.)
fn append_constant_symbol_list_gives_clean_error() {
use super::common::compile_expect_error;
let err = compile_expect_error(&wrap_with_runtime_ratio("(append '(a b) (cons X nil))"));
assert!(
!err.contains("Undefined symbol"),
"expected a clean stack-value error, got: {err}"
err.contains("cannot compile") || err.contains("WASM stack value"),
"got: {err}"
fn pair_p_true_on_constant_list() {
compile_and_validate("(pair? '(1 2 3))");
fn pair_p_false_on_nil() {
compile_and_validate("(pair? nil)");
fn pair_p_runtime_chain() {
compile_and_validate(&wrap_with_runtime_ratio("(pair? (cons X nil))"));
fn pair_p_in_value_position() {
compile_and_validate("(defun is-pair (x) (pair? x)) (is-pair '(1))");
// MAP / FILTER / FOLD with a callback whose body isn't constant-foldable
// (produces a runtime value or a side effect). The const-folders used to
// either crash (FOLD: serialize a placeholder with no stack producer) or
// silently bake the placeholder in / drop the side effect (MAP/FILTER). They
// now detect a runtime callback result and route to per-element runtime
// lowering. Pre-existing bug class, fixed together.
fn fold_nil_seed_build_list_over_runtime_list() {
// nil-seeded accumulator built into a list over a runtime chain — the
// accumulator is sized from the body's PairRef return, nil seeds to a
// null pair (was: "init type bool doesn't match accumulator type").
"(fold (lambda (acc x) (cons x acc)) nil (cons X nil))",
fn fold_print_body_runs_at_runtime() {
compile_and_validate("(fold (lambda (acc x) (print x)) nil '(1 2))");
fn map_print_body_emits_per_element() {
let with = compile_and_validate("(map (lambda (x) (print \"yyyyyyyy\")) '(1 2 3))");
let without = compile_and_validate("(map (lambda (x) x) '(1 2 3))");
with.len() > without.len(),
"map per-element print dropped: {} vs {}",
with.len(),
without.len()
fn filter_print_body_emits_per_element() {
let with = compile_and_validate("(filter (lambda (x) (print \"yyyyyyyy\")) '(1 2 3))");
let without = compile_and_validate("(filter (lambda (x) #t) '(1 2 3))");
"filter per-element print dropped: {} vs {}",
fn map_runtime_body_over_constant_list() {
compile_and_validate("(map (lambda (x) (transaction-post-date 0)) '(1 2))");
fn filter_runtime_predicate_over_constant_list() {
compile_and_validate("(filter (lambda (x) (transaction-is-multi-currency 0)) '(1 2))");
// Runtime-bail edge cases (HOF review round). Each was rejected/mis-lowered
// after the initial const-fold→runtime bail; now handled.
/// FOLD forced onto the runtime path (side-effecting body) with a NON-nil
/// constant-list accumulator seed. The seed is materialized into a runtime
/// `$pair` chain (was: "cannot compile to WASM stack value: '(1)").
fn fold_runtime_body_with_constant_list_seed() {
compile_and_validate("(fold (lambda (acc x) (print x) acc) '(1) '(2))");
/// MAP whose per-element results have DIFFERENT types widens the result chain
/// to `AnyRef` (ADR-0025) instead of erroring "heterogeneous result elements".
fn map_heterogeneous_results_widen_to_anyref() {
compile_and_validate("(map (lambda (x) (if (= x 1) (print x) (tag-name 0))) '(1 2))");
/// FILTER over a heterogeneous constant list (ratio + string) widens the kept
/// chain to `AnyRef` instead of erroring "heterogeneous element types".
fn filter_heterogeneous_list_widens_to_anyref() {
compile_and_validate("(filter (lambda (x) (print x)) '(1 \"a\"))");
/// MAP over MULTIPLE constant lists with a non-constant-foldable body lowers
/// row-by-row at runtime (was: "requires exactly one list argument").
fn map_multi_list_runtime_body() {
compile_and_validate("(map (lambda (x y) (print x)) '(1 2) '(3 4))");
/// Multi-list MAP that IS constant-foldable still folds.
fn map_multi_list_constant_folds() {
compile_and_validate("(map (lambda (a b) (+ a b)) '(1 2) '(3 4))");
/// ADR-0028: a FOLD over a let-bound closure, bound in turn in a `let`, sizes
/// the binding local from the COMPILED (closure-sig) accumulator type. The
/// eval side can't see the closure sig and falls back to the integer seed's
/// type (Index); without sizing from codegen the binding would `local.set` a
/// Ratio value into an I32 local — invalid wasm.
fn fold_closure_result_bound_in_let_compiles() {
"(let* ((f (lambda (a x) (+ a x))) \
(acc (fold f 0 (cons (transaction-post-date 0) nil)))) \
acc)",
/// A wrong-arity closure used as a FILTER predicate over an EMPTY list is still
/// arity-checked — no element reaches `compile_call_ref`, so the empty-list
/// fast path validates the predicate's arity itself.
fn filter_wrong_arity_closure_over_empty_list_errors() {
let err = compile_expect_error("(let ((f (lambda (a b) (< a b)))) (filter f '()))");
err.to_lowercase().contains("arity") || err.contains("closure") || err.contains("argument"),
"expected an arity error, got: {err}"