Lines
90.48 %
Functions
20 %
Branches
100 %
//! `LIST` — multi-arg list constructor. Desugars to a `$pair` chain
//! when any argument is a runtime value (so the result rides a `$pair`
//! chain rather than being held as a compile-time `Expr::List`) by calling
//! the CONS builder functions DIRECTLY — never by synthesizing a `(CONS …)`
//! form, which would route through ordinary symbol dispatch and could hit a
//! user `(defun cons …)` shadow.
use crate::ast::{Expr, WasmType};
use crate::compiler::context::CompileContext;
use crate::compiler::emit::FunctionEmitter;
use crate::compiler::expr::{compile_expr, eval_value, serialize_stack_to_output};
use crate::error::Result;
use crate::runtime::SymbolTable;
use super::cons::{compile_pair_chain, cons};
pub(super) fn list(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
let resolved: Vec<Expr> = args
.iter()
.map(|a| eval_value(symbols, a))
.collect::<Result<_>>()?;
// A runtime element can't ride a quoted compile-time list: consumers
// (CAR/CDR/…) would extract a bare `WasmRuntime` placeholder with no real
// stack value and emit invalid wasm. Fold the ALREADY-RESOLVED elements
// right-to-left through the CONS builder so the result is a proper
// `WasmRuntime(PairRef(elem))` — exactly what CONS yields. Folding the
// resolved values (not the source `args`) keeps element side effects
// single-applied (they ran once during the eval above), and calling `cons`
// directly bypasses any `CONS` symbol shadow.
if resolved.iter().any(Expr::is_wasm_runtime) {
let mut chain = Expr::Nil;
for elem in resolved.iter().rev() {
chain = cons(symbols, &[elem.clone(), chain])?;
}
return Ok(chain);
Ok(Expr::Quote(Box::new(Expr::List(resolved))))
pub(super) fn compile_list(
ctx: &mut CompileContext,
emit: &mut FunctionEmitter,
symbols: &mut SymbolTable,
args: &[Expr],
) -> Result<()> {
// Classify runtime-ness on a CLONE so the live symbol table is mutated by
// exactly ONE resolve pass — the chosen branch below. (Calling `list()` on
// the live table here, then `compile_pair_chain` again, would resolve the
// element expressions twice on the live table.)
if any_runtime_arg(&mut symbols.clone(), args)? {
let ty = compile_list_to_stack(ctx, emit, symbols, args)?;
serialize_stack_to_output(ctx, emit, ty)?;
return Ok(());
let folded = list(symbols, args)?;
compile_expr(ctx, emit, symbols, &folded)
fn any_runtime_arg(symbols: &mut SymbolTable, args: &[Expr]) -> Result<bool> {
for arg in args {
if eval_value(symbols, arg)?.is_wasm_runtime() {
return Ok(true);
Ok(false)
/// Emit `(list …)` as a `$pair` chain. Resolves each element ONCE, then folds
/// right-to-left through `compile_cons_to_stack` — building each pair as a
/// two-element arg slice `[car, <inner-chain>]` whose cdr is the already-built
/// inner chain expression. Calls the CONS builder directly (no `(CONS …)`
/// form, hence no symbol-dispatch / shadow surface) and threads the resolved
/// car values so element side effects are applied exactly once.
pub(super) fn compile_list_to_stack(
) -> Result<WasmType> {
compile_pair_chain(ctx, emit, symbols, args)