Skip to main content

cljrs_runtime/interp/
eval.rs

1//! Top-level `eval` dispatcher and form-to-value conversion.
2
3use std::sync::Arc;
4
5use crate::builtins::form::{expand_pairs, expand_reader_conds, expand_reader_conds_cow};
6use crate::builtins::special::SPECIAL_FORMS;
7use crate::env::env::{Env, GlobalEnv};
8use crate::env::error::{EvalError, EvalResult};
9use crate::interp::apply::eval_call;
10use crate::interp::special::eval_special;
11use crate::interp::syntax_quote::syntax_quote;
12use cljrs_gc::GcPtr;
13use cljrs_reader::Form;
14use cljrs_reader::form::FormKind;
15use cljrs_value::regex::Pattern;
16use cljrs_value::value::SetValue;
17use cljrs_value::{
18    CljxFuture, FutureState, Keyword, MapValue, PersistentHashSet, PersistentList,
19    PersistentVector, Symbol, Value,
20};
21
22/// Evaluate a `Form` in the given `Env`.
23pub fn eval(form: &Form, env: &mut Env) -> EvalResult {
24    if !crate::env::gas::charge(1) {
25        return Err(EvalError::GasExhausted);
26    }
27    match &form.kind {
28        // ── Atoms ─────────────────────────────────────────────────────────
29        FormKind::Nil => Ok(Value::Nil),
30        FormKind::Bool(b) => Ok(Value::Bool(*b)),
31        FormKind::Int(n) => Ok(Value::Long(*n)),
32        FormKind::Float(f) => Ok(Value::Double(*f)),
33        FormKind::Symbolic(f) => Ok(Value::Double(*f)), // ##Inf etc.
34        FormKind::Str(s) => Ok(Value::string(s.clone())),
35        FormKind::Char(c) => Ok(Value::Char(*c)),
36        FormKind::BigInt(s) => crate::builtins::parse_bigint(s),
37        FormKind::BigDecimal(s) => crate::builtins::parse_bigdecimal(s),
38        FormKind::Ratio(s) => crate::builtins::parse_ratio(s),
39        FormKind::Regex(s) => {
40            let r = Pattern::new(s);
41            match r {
42                Ok(r) => Ok(Value::Pattern(GcPtr::new(r))),
43                Err(e) => Err(EvalError::Runtime(e.to_string())),
44            }
45        }
46
47        // ── Identifiers ───────────────────────────────────────────────────
48        FormKind::Symbol(s) => eval_symbol(s, env),
49        FormKind::Keyword(s) => Ok(Value::keyword(Keyword::parse(s))),
50        FormKind::AutoKeyword(s) => {
51            let full = env
52                .globals
53                .resolve_auto_keyword(&env.current_ns, s)
54                .map_err(EvalError::Runtime)?;
55            Ok(Value::keyword(Keyword::parse(&full)))
56        }
57        // A symbol key from an auto-resolved namespaced map: in evaluated
58        // position it names a var, exactly as a written-out symbol key does.
59        FormKind::AutoSymbol(s) => {
60            let full = env
61                .globals
62                .resolve_auto_keyword(&env.current_ns, s)
63                .map_err(EvalError::Runtime)?;
64            eval_symbol(&full, env)
65        }
66
67        // ── Collections ───────────────────────────────────────────────────
68        FormKind::List(forms) => eval_list(forms, env),
69        FormKind::Vector(forms) => {
70            let forms = expand_reader_conds_cow(forms);
71            let mut vals: Vec<Value> = Vec::with_capacity(forms.len());
72            for f in forms.iter() {
73                let _root = crate::env::gc_roots::root_values(&vals);
74                vals.push(eval(f, env)?);
75            }
76            Ok(Value::Vector(GcPtr::new(PersistentVector::from_iter(vals))))
77        }
78        FormKind::Map(forms) => {
79            let forms = expand_pairs(forms).map_err(|_| {
80                EvalError::Runtime("map literal must have an even number of forms".into())
81            })?;
82            let mut pairs: Vec<Value> = Vec::with_capacity(forms.len());
83            for f in forms.iter() {
84                let _root = crate::env::gc_roots::root_values(&pairs);
85                pairs.push(eval(f, env)?);
86            }
87            let kv_pairs: Vec<(Value, Value)> = pairs
88                .chunks(2)
89                .map(|pair| (pair[0].clone(), pair[1].clone()))
90                .collect();
91            Ok(Value::Map(MapValue::from_pairs(kv_pairs)))
92        }
93        FormKind::Set(forms) => {
94            let forms = expand_reader_conds_cow(forms);
95            let mut vals: Vec<Value> = Vec::with_capacity(forms.len());
96            for f in forms.iter() {
97                let _root = crate::env::gc_roots::root_values(&vals);
98                vals.push(eval(f, env)?);
99            }
100            Ok(Value::Set(SetValue::Hash(GcPtr::new(
101                PersistentHashSet::from_iter(vals),
102            ))))
103        }
104
105        // ── Reader macros ─────────────────────────────────────────────────
106        // `'x` sugar: like the `quote` special form, `::kw` and an
107        // auto-resolved map's symbol keys resolve against the reading
108        // namespace before the form becomes data.
109        FormKind::Quote(inner) => {
110            let resolved = crate::builtins::form::resolve_auto_forms(inner, env)?;
111            crate::builtins::form::form_to_value(&resolved)
112        }
113        FormKind::SyntaxQuote(inner) => syntax_quote(inner, env),
114        FormKind::Unquote(_) => Err(EvalError::Runtime("unquote outside syntax-quote".into())),
115        FormKind::UnquoteSplice(_) => Err(EvalError::Runtime(
116            "unquote-splice outside syntax-quote".into(),
117        )),
118        FormKind::Deref(inner) => {
119            let v = eval(inner, env)?;
120            if env.is_async && matches!(v, Value::Future(_)) {
121                return Err(EvalError::Runtime(
122                    "deref (@) on a future is not allowed inside an ^:async function; use (await ...) instead".into(),
123                ));
124            }
125            deref_value(v)
126        }
127        FormKind::Var(inner) => {
128            if let FormKind::Symbol(s) = &inner.kind {
129                let parsed = Symbol::parse(s);
130                let ns: Arc<str> = env.resolve_ns_or_current(parsed.namespace.as_deref());
131                env.globals
132                    .lookup_var_in_ns(&ns, &parsed.name)
133                    .map(Value::Var)
134                    .ok_or_else(|| EvalError::UnboundSymbol(s.clone()))
135            } else {
136                Err(EvalError::Runtime("var requires a symbol".into()))
137            }
138        }
139        FormKind::Meta(meta, form) => {
140            // `^m expr` evaluates `expr`; the annotation becomes runtime
141            // metadata only on a form that constructs an `IObj` (a collection
142            // literal or an `fn`). Everywhere else it is a compile-time hint,
143            // and — as on the JVM — is not evaluated at all.
144            //
145            // `lower::anf` applies the same rule, so a promoted or AOT-compiled
146            // body answers `meta` the same way an interpreted one does.
147            let mut value = eval(form, env)?;
148            // `^:async (fn …)` is both: it makes the fn async, and — an `fn`
149            // taking runtime metadata — lands `{:async true}` on it.
150            // A stacked annotation (`^:async ^:a (fn …)`) has already wrapped
151            // the fresh fn. One unwrap suffices: `Value::with_meta` flattens,
152            // so stacked annotations never nest `WithMeta`.
153            if meta.requests_async() && form.unmeta().is_fn_form() {
154                let target = match &mut value {
155                    Value::WithMeta(inner, _) => inner.as_mut(),
156                    other => other,
157                };
158                if let Value::Fn(f) = target {
159                    f.get_mut().is_async = true;
160                }
161            }
162            if !form.takes_runtime_meta() {
163                return Ok(value);
164            }
165            let m = crate::builtins::form::expand_meta_annotation(meta, &mut |f| eval(f, env))?;
166            Ok(crate::builtins::form::attach_meta(value, m))
167        }
168
169        // ── Dispatch ──────────────────────────────────────────────────────
170        FormKind::AnonFn(body) => {
171            let expanded = crate::builtins::form::expand_anon_fn(body, form.span.clone());
172            eval(&expanded, env)
173        }
174        FormKind::ReaderCond {
175            splicing: _,
176            clauses,
177        } => eval_reader_cond(clauses, env),
178        FormKind::TaggedLiteral(tag, inner) => eval_tagged_literal(tag, inner, env),
179    }
180}
181
182/// Evaluate a form with a cooperative execution-credit budget.
183///
184/// Nested tree-walker, IR-interpreter, and JIT work shares this budget.  The
185/// existing [`eval`] entry point remains unmetered unless called inside this
186/// dynamic scope.
187pub fn eval_with_gas(form: &Form, env: &mut Env, credits: u64) -> EvalResult {
188    let meter = crate::env::gas::GasMeter::new(credits);
189    let _guard = crate::env::gas::GasGuard::install(meter);
190    eval(form, env)
191}
192
193// ── List / call dispatch ──────────────────────────────────────────────────────
194
195fn eval_list(forms: &[Form], env: &mut Env) -> EvalResult {
196    if forms.is_empty() {
197        return Ok(Value::List(GcPtr::new(PersistentList::empty())));
198    }
199
200    // Expand reader conditionals (both splicing and non-splicing) before dispatch.
201    let expanded: Vec<Form>;
202    let forms: &[Form] = if forms
203        .iter()
204        .any(|f| matches!(f.kind, FormKind::ReaderCond { .. }))
205    {
206        expanded = expand_reader_conds(forms);
207        if expanded.is_empty() {
208            return Ok(Value::List(GcPtr::new(PersistentList::empty())));
209        }
210        &expanded
211    } else {
212        forms
213    };
214
215    // Check for special form.
216    if let Some(s) = forms[0].as_symbol()
217        && is_special_form(s)
218    {
219        return eval_special(s, &forms[1..], env);
220    }
221
222    eval_call(&forms[0], &forms[1..], env)
223}
224
225// ── Symbol resolution ─────────────────────────────────────────────────────────
226
227fn eval_symbol(s: &str, env: &mut Env) -> EvalResult {
228    let sym = Symbol::parse(s);
229
230    // Explicit version suffix (`name@hash` or `ns/name@hash`): always a
231    // namespace-level lookup — no local-frame fallback.
232    #[cfg(not(target_arch = "wasm32"))]
233    if let Some(ref commit) = sym.version.clone() {
234        crate::env::policy::check_versioned_lookup()?;
235        return crate::interp::versioned::resolve_versioned_symbol(&sym, commit, env);
236    }
237    #[cfg(target_arch = "wasm32")]
238    if sym.version.is_some() {
239        return Err(crate::env::error::EvalError::Runtime(
240            "versioned symbols are not supported in WASM".to_string(),
241        ));
242    }
243
244    // Local frames (params, let-bindings, closed-over vars) take priority for
245    // unversioned symbols.
246    if let Some(v) = env.lookup_local_frames(s) {
247        return Ok(v);
248    }
249
250    // Inherited versioned context: if we are evaluating inside a versioned
251    // function body, unversioned same-namespace symbols resolve at the inherited
252    // commit rather than HEAD.  "Same namespace" includes a qualified
253    // self-reference written with the base name (`mylib/x` inside `mylib@hash`).
254    #[cfg(not(target_arch = "wasm32"))]
255    if let Some(commit) = env.versioned_eval_commit.clone() {
256        let is_same_ns = sym.namespace.is_none()
257            || sym.namespace.as_deref() == Some(env.current_ns.as_ref())
258            || sym.namespace.as_deref()
259                == Some(crate::env::versioned::base_ns_name(&env.current_ns));
260        if is_same_ns {
261            return crate::interp::versioned::resolve_versioned_symbol(&sym, &commit, env);
262        }
263    }
264
265    // Fall through to normal global namespace lookup.
266    if let Some(v) = env.globals.lookup_in_ns(&env.current_ns, s) {
267        return Ok(v);
268    }
269
270    // Namespace-qualified external symbol: `ns/name`
271    if s.contains('/')
272        && !s.starts_with('/')
273        && let Some(ns_part) = &sym.namespace
274    {
275        let resolved: Arc<str> = env.resolve_ns_part(ns_part);
276        // Qualified self-reference inside a versioned namespace: `mylib/x`
277        // written in `mylib@hash`'s own source resolves at the pinned commit,
278        // i.e. inside the versioned namespace itself.
279        #[cfg(not(target_arch = "wasm32"))]
280        let resolved: Arc<str> = if env.current_ns.as_ref() != resolved.as_ref()
281            && crate::env::versioned::base_ns_name(&env.current_ns) == resolved.as_ref()
282        {
283            env.current_ns.clone()
284        } else {
285            resolved
286        };
287        if resolved.as_ref() != env.current_ns.as_ref()
288            && let Some(var) = env.globals.lookup_var(&resolved, &sym.name)
289            && GlobalEnv::var_is_private(var.get())
290        {
291            return Err(EvalError::Runtime(format!(
292                "var: {}/{} is not public",
293                resolved, sym.name
294            )));
295        }
296        return env
297            .globals
298            .lookup_in_ns(&resolved, &sym.name)
299            .ok_or_else(|| EvalError::UnboundSymbol(s.to_string()));
300    }
301
302    // JVM class names resolve to themselves as symbols (for instance?, catch, etc.)
303    if is_jvm_class_name(s) {
304        return Ok(Value::symbol(Symbol::simple(s)));
305    }
306
307    Err(EvalError::UnboundSymbol(s.to_string()))
308}
309
310/// Recognise JVM-style class names used in Clojure for `instance?`, `catch`, etc.
311pub fn is_jvm_class_name(s: &str) -> bool {
312    matches!(
313        s,
314        "clojure.lang.BigInt"
315            | "java.math.BigDecimal"
316            | "java.math.BigInteger"
317            | "clojure.lang.Ratio"
318            | "java.lang.Long"
319            | "java.lang.Double"
320            | "java.lang.String"
321            | "java.lang.Boolean"
322            | "java.lang.Character"
323            | "java.lang.Number"
324            | "clojure.lang.Symbol"
325            | "clojure.lang.Keyword"
326            | "clojure.lang.PersistentList"
327            | "clojure.lang.PersistentVector"
328            | "clojure.lang.PersistentHashMap"
329            | "clojure.lang.PersistentHashSet"
330            | "clojure.lang.PersistentArrayMap"
331            | "clojure.lang.IFn"
332            | "clojure.lang.ISeq"
333            | "clojure.lang.IPending"
334            | "clojure.lang.Atom"
335            | "clojure.lang.Var"
336            | "clojure.lang.Namespace"
337            | "java.util.UUID"
338            | "java.lang.Exception"
339            | "java.lang.Throwable"
340            | "java.lang.Error"
341            | "Exception"
342            | "Throwable"
343            | "Error"
344            | "clojure.lang.ExceptionInfo"
345            | "clojure.lang.IEditableCollection"
346            | "Boolean"
347            | "clojure.lang.PersistentQueue"
348            | "java.util.regex.Pattern"
349    )
350}
351
352// ── is_special_form ───────────────────────────────────────────────────────────
353
354pub fn is_special_form(s: &str) -> bool {
355    SPECIAL_FORMS.contains(&s)
356}
357
358// ── eval_body ─────────────────────────────────────────────────────────────────
359
360/// Dereference a value: used by `@x` reader macro and the `deref` builtin.
361pub fn deref_value(v: Value) -> EvalResult {
362    match v {
363        Value::Atom(a) => Ok(a.get().deref()),
364        Value::SharedAtom(sa) => Ok(cljrs_value::demote(&sa.deref_val())),
365        Value::Var(var) => crate::env::dynamics::deref_var(&var)
366            .ok_or_else(|| EvalError::Runtime("unbound var".into())),
367        Value::Volatile(vol) => Ok(vol.get().deref()),
368        Value::Delay(d) => d.get().force().map_err(EvalError::Runtime),
369        Value::Agent(a) => Ok(a.get().get_state()),
370        Value::Reduced(inner) => Ok(*inner),
371        Value::Promise(p) => Ok(p.get().deref_blocking()),
372        Value::Future(f) => {
373            let mut guard = f.get().state.lock().unwrap();
374            loop {
375                match &*guard {
376                    FutureState::Done(v) => {
377                        f.get().mark_observed();
378                        return Ok(v.clone());
379                    }
380                    FutureState::Failed(v) => {
381                        f.get().mark_observed();
382                        return Err(EvalError::Thrown(v.clone()));
383                    }
384                    FutureState::GasExhausted => {
385                        f.get().mark_observed();
386                        return Err(EvalError::GasExhausted);
387                    }
388                    FutureState::Cancelled => {
389                        return Err(EvalError::Thrown(CljxFuture::cancelled_error()));
390                    }
391                    FutureState::Running => {
392                        guard = f.get().cond.wait(guard).unwrap();
393                    }
394                }
395            }
396        }
397        other => Err(EvalError::Runtime(format!(
398            "cannot deref {}",
399            other.type_name()
400        ))),
401    }
402}
403
404/// Evaluate a sequence of forms and return the value of the last one.
405pub fn eval_body(forms: &[Form], env: &mut Env) -> EvalResult {
406    let mut result = Value::Nil;
407    for form in forms {
408        result = eval(form, env)?;
409    }
410    Ok(result)
411}
412
413// ── reader cond ───────────────────────────────────────────────────────────────
414
415fn eval_reader_cond(clauses: &[Form], env: &mut Env) -> EvalResult {
416    // clauses = [kw form kw form ...]
417    let mut i = 0;
418    let mut default: Option<&Form> = None;
419    while i + 1 < clauses.len() {
420        match &clauses[i].kind {
421            FormKind::Keyword(k) if k == "rust" => {
422                return eval(&clauses[i + 1], env);
423            }
424            FormKind::Keyword(k) if k == "default" => {
425                default = Some(&clauses[i + 1]);
426            }
427            _ => {}
428        }
429        i += 2;
430    }
431    match default {
432        Some(f) => eval(f, env),
433        None => Ok(Value::Nil),
434    }
435}
436
437// ── tagged literals ──────────────────────────────────────────────────────────
438
439fn eval_tagged_literal(tag: &str, inner: &Form, env: &mut Env) -> EvalResult {
440    match tag {
441        "uuid" => {
442            let val = eval(inner, env)?;
443            match &val {
444                Value::Str(s) => {
445                    let uuid = uuid::Uuid::parse_str(s.get())
446                        .map_err(|e| EvalError::Runtime(format!("invalid UUID: {e}")))?;
447                    Ok(Value::Uuid(uuid.as_u128()))
448                }
449                _ => Err(EvalError::Runtime(format!(
450                    "#uuid expects a string, got {}",
451                    val.type_name()
452                ))),
453            }
454        }
455        "inst" => {
456            // TODO: implement #inst for date/time literals
457            let val = eval(inner, env)?;
458            Ok(val)
459        }
460        _ => Err(EvalError::Runtime(format!(
461            "unknown tagged literal: #{tag}"
462        ))),
463    }
464}
465
466// ── Tests ─────────────────────────────────────────────────────────────────────
467
468#[cfg(test)]
469mod tests {
470    use super::*;
471    use crate::env::env::GlobalEnv;
472    use std::sync::Arc;
473
474    fn make_env() -> (Arc<GlobalEnv>, Env) {
475        let globals = crate::Runtime::builder()
476            .execution_mode(crate::ExecutionMode::TreeWalk)
477            .eager_clojure_test(true)
478            .build()
479            .expect("runtime")
480            .into_globals();
481        let env = Env::new(globals.clone(), "user");
482        (globals, env)
483    }
484
485    fn eval_str(src: &str) -> EvalResult {
486        let (_, mut env) = make_env();
487        eval_src(src, &mut env)
488    }
489
490    fn eval_src(src: &str, env: &mut Env) -> EvalResult {
491        let mut parser = cljrs_reader::Parser::new(src.to_string(), "<test>".to_string());
492        let forms = parser.parse_all().map_err(EvalError::Read)?;
493        let mut result = Value::Nil;
494        for form in forms {
495            result = eval(&form, env)?;
496        }
497        Ok(result)
498    }
499
500    fn long(n: i64) -> Value {
501        Value::Long(n)
502    }
503    fn bool_v(b: bool) -> Value {
504        Value::Bool(b)
505    }
506
507    // ── Atoms ─────────────────────────────────────────────────────────────
508
509    #[test]
510    fn test_literal_int() {
511        assert_eq!(eval_str("42").unwrap(), long(42));
512    }
513
514    #[test]
515    fn test_literal_string() {
516        assert!(matches!(eval_str("\"hello\"").unwrap(), Value::Str(_)));
517    }
518
519    #[test]
520    fn test_literal_nil() {
521        assert_eq!(eval_str("nil").unwrap(), Value::Nil);
522    }
523
524    #[test]
525    fn test_literal_true() {
526        assert_eq!(eval_str("true").unwrap(), bool_v(true));
527    }
528
529    #[test]
530    fn test_literal_false() {
531        assert_eq!(eval_str("false").unwrap(), bool_v(false));
532    }
533
534    // ── Arithmetic ────────────────────────────────────────────────────────
535
536    #[test]
537    fn test_add() {
538        assert_eq!(eval_str("(+ 1 2)").unwrap(), long(3));
539    }
540
541    #[test]
542    fn test_mul() {
543        assert_eq!(eval_str("(* 2 3)").unwrap(), long(6));
544    }
545
546    #[test]
547    fn test_div_exact() {
548        assert_eq!(eval_str("(/ 10 2)").unwrap(), long(5));
549    }
550
551    #[test]
552    fn test_sub() {
553        assert_eq!(eval_str("(- 10 3)").unwrap(), long(7));
554    }
555
556    // ── let ───────────────────────────────────────────────────────────────
557
558    #[test]
559    fn test_let_simple() {
560        assert_eq!(eval_str("(let* [x 1 y 2] (+ x y))").unwrap(), long(3));
561    }
562
563    #[test]
564    fn test_let_shadowing() {
565        assert_eq!(eval_str("(let* [x 1] (let* [x 10] x))").unwrap(), long(10));
566    }
567
568    // ── fn + call ─────────────────────────────────────────────────────────
569
570    #[test]
571    fn test_fn_call() {
572        assert_eq!(eval_str("((fn* [x] (* x x)) 5)").unwrap(), long(25));
573    }
574
575    #[test]
576    fn test_closure_capture() {
577        assert_eq!(
578            eval_str("(let* [n 3] ((fn* [x] (+ x n)) 4))").unwrap(),
579            long(7)
580        );
581    }
582
583    #[test]
584    fn test_multi_arity_fn() {
585        assert_eq!(
586            eval_str("((fn* ([x] x) ([x y] (+ x y))) 1 2)").unwrap(),
587            long(3)
588        );
589    }
590
591    // ── recur / loop ──────────────────────────────────────────────────────
592
593    #[test]
594    fn test_loop_recur() {
595        let result =
596            eval_str("(loop* [i 0 acc 0] (if (= i 5) acc (recur (inc i) (+ acc i))))").unwrap();
597        assert_eq!(result, long(10));
598    }
599
600    // ── def / defn ────────────────────────────────────────────────────────
601
602    #[test]
603    fn test_def() {
604        let (_, mut env) = make_env();
605        eval_src("(def x 42)", &mut env).unwrap();
606        assert_eq!(eval_src("x", &mut env).unwrap(), long(42));
607    }
608
609    #[test]
610    fn test_defn() {
611        let (_, mut env) = make_env();
612        eval_src("(defn square [x] (* x x))", &mut env).unwrap();
613        assert_eq!(eval_src("(square 7)", &mut env).unwrap(), long(49));
614    }
615
616    // ── if ────────────────────────────────────────────────────────────────
617
618    #[test]
619    fn test_if_truthy() {
620        assert_eq!(eval_str("(if true 1 2)").unwrap(), long(1));
621    }
622
623    #[test]
624    fn test_if_falsy() {
625        assert_eq!(eval_str("(if false 1 2)").unwrap(), long(2));
626    }
627
628    #[test]
629    fn test_if_nil_branch() {
630        assert_eq!(eval_str("(if false 1)").unwrap(), Value::Nil);
631    }
632
633    // ── do ────────────────────────────────────────────────────────────────
634
635    #[test]
636    fn test_do() {
637        assert_eq!(eval_str("(do 1 2 3)").unwrap(), long(3));
638    }
639
640    // ── quote ─────────────────────────────────────────────────────────────
641
642    #[test]
643    fn test_quote_list() {
644        let v = eval_str("'(1 2 3)").unwrap();
645        assert!(matches!(v, Value::List(_)));
646    }
647
648    // ── keyword lookup ────────────────────────────────────────────────────
649
650    #[test]
651    fn test_keyword_lookup() {
652        assert_eq!(eval_str("(:a {:a 1})").unwrap(), long(1));
653    }
654
655    #[test]
656    fn test_keyword_lookup_missing() {
657        assert_eq!(eval_str("(:b {:a 1})").unwrap(), Value::Nil);
658    }
659
660    // ── Map / Vector / Set literals ───────────────────────────────────────
661
662    #[test]
663    fn test_map_literal() {
664        let v = eval_str("{:a 1 :b 2}").unwrap();
665        assert!(matches!(v, Value::Map(_)));
666        if let Value::Map(m) = &v {
667            assert_eq!(m.count(), 2);
668        }
669    }
670
671    #[test]
672    fn test_vector_literal() {
673        let v = eval_str("[1 2 3]").unwrap();
674        assert!(matches!(v, Value::Vector(_)));
675    }
676
677    #[test]
678    fn test_set_literal() {
679        let v = eval_str("#{1 2 3}").unwrap();
680        assert!(matches!(v, Value::Set(_)));
681    }
682
683    #[test]
684    fn test_contains_q_vector_non_integer_key_returns_false() {
685        // Regression for #206: non-integer key on a vector must return false,
686        // not throw a WrongType error.
687        assert_eq!(eval_str("(contains? [1 2 3] :a)").unwrap(), bool_v(false));
688        assert_eq!(
689            eval_str("(contains? [1 2 3] \"x\")").unwrap(),
690            bool_v(false)
691        );
692        // Integer keys still work correctly.
693        assert_eq!(eval_str("(contains? [1 2 3] 0)").unwrap(), bool_v(true));
694        assert_eq!(eval_str("(contains? [1 2 3] 9)").unwrap(), bool_v(false));
695    }
696
697    // ── set! ──────────────────────────────────────────────────────────────
698
699    #[test]
700    fn test_set_bang() {
701        let (_, mut env) = make_env();
702        eval_src("(def x 1)", &mut env).unwrap();
703        eval_src("(set! x 99)", &mut env).unwrap();
704        assert_eq!(eval_src("x", &mut env).unwrap(), long(99));
705    }
706
707    // ── throw / try / catch ───────────────────────────────────────────────
708
709    #[test]
710    fn test_throw_catch() {
711        let v = eval_str("(try (throw (ex-info \"oops\" {})) (catch Exception e (ex-message e)))")
712            .unwrap();
713        assert!(matches!(v, Value::Str(_)));
714    }
715
716    #[test]
717    fn test_try_no_throw() {
718        assert_eq!(eval_str("(try 42)").unwrap(), long(42));
719    }
720
721    #[test]
722    fn test_catch_default_catches_internal_value_errors() {
723        // Regression for #168: internal `ValueError`s (IndexOutOfBounds, WrongType)
724        // raised by core ops must be caught by the ClojureScript `:default` keyword,
725        // not just by symbol catch types.
726        for src in [
727            "(try (nth [1 2 3] 10) (catch :default e \"caught\"))",
728            "(try (aget (long-array 3) 10) (catch :default e \"caught\"))",
729            "(try (aset (long-array 3) 10 5) (catch :default e \"caught\"))",
730        ] {
731            let v = eval_str(src).unwrap();
732            assert_eq!(v, Value::string("caught"), "{src}");
733        }
734    }
735
736    #[test]
737    fn test_catch_default_binds_clean_ex_message() {
738        // The caught value is a normalized exception: `ex-message` returns the
739        // plain `ValueError` text with no `runtime error:` prefix, matching how a
740        // user `throw` / `ex-info` value behaves.
741        let v = eval_str("(try (nth [1 2 3] 10) (catch :default e (ex-message e)))").unwrap();
742        assert_eq!(v, Value::string("index out of bounds: 10 >= 3"));
743    }
744
745    #[test]
746    fn test_finally_runs_and_value_is_discarded() {
747        // finally executes for its side effect, but the `try` value is the body's.
748        let v = eval_str("(let [a (atom 0)] (try 1 (finally (reset! a 5))) @a)").unwrap();
749        assert_eq!(v, long(5));
750        assert_eq!(eval_str("(try 1 (finally 2))").unwrap(), long(1));
751    }
752
753    #[test]
754    fn test_finally_runs_after_catch() {
755        let v = eval_str(
756            "(let [a (atom 0)]
757               (try (throw (ex-info \"x\" {})) (catch Exception e :caught)
758                 (finally (reset! a 9)))
759               @a)",
760        )
761        .unwrap();
762        assert_eq!(v, long(9));
763    }
764
765    #[test]
766    fn test_finally_exception_propagates() {
767        // An exception thrown from `finally` supersedes the body's result.
768        assert!(eval_str("(try 1 (finally (throw (ex-info \"boom\" {}))))").is_err());
769    }
770
771    #[test]
772    fn test_catch_reader_conditional_type() {
773        // Regression for #210: a reader conditional in the catch type position must
774        // be resolved before matching — previously the catch clause was silently
775        // dropped and the exception escaped.
776
777        // Single-branch: #?(:rust Exception)
778        let v =
779            eval_str(r#"(try (throw (ex-info "boom" {})) (catch #?(:rust Exception) e :caught))"#)
780                .unwrap();
781        let kw_caught = Value::keyword(cljrs_value::Keyword::simple("caught"));
782        assert_eq!(v, kw_caught, "single-branch reader cond");
783
784        // Multi-branch: the :rust branch must win over :clj/:cljs/:default ordering
785        let v = eval_str(concat!(
786            "(try (throw (ex-info \"boom\" {})) ",
787            "(catch #?(:clj Throwable :cljs :default :rust Exception) e :caught))",
788        ))
789        .unwrap();
790        assert_eq!(v, kw_caught, "multi-branch reader cond");
791
792        // A reader conditional whose :rust branch resolves to :default is also a
793        // catch-all and must match any exception.
794        let v =
795            eval_str(r#"(try (throw (ex-info "boom" {})) (catch #?(:rust :default) e :caught))"#)
796                .unwrap();
797        assert_eq!(v, kw_caught, "reader cond resolves to :default");
798
799        // A reader conditional with no :rust branch must NOT catch the exception.
800        let result =
801            eval_str(r#"(try (throw (ex-info "boom" {})) (catch #?(:clj Throwable) e :caught))"#);
802        assert!(
803            result.is_err(),
804            "no matching :rust branch must let exception escape"
805        );
806    }
807
808    #[test]
809    fn test_nth_negative_index() {
810        // Negative index returns the not-found default, or throws without one.
811        assert_eq!(
812            eval_str("(= (nth [10 20 30] -1 :nf) :nf)").unwrap(),
813            bool_v(true)
814        );
815        assert!(eval_str("(nth [10 20 30] -1)").is_err());
816        assert!(eval_str("(nth '(1 2 3) -1)").is_err());
817        // Crucially, must NOT hang walking an infinite lazy seq to usize::MAX.
818        assert_eq!(
819            eval_str("(= (nth (range) -1 :nf) :nf)").unwrap(),
820            bool_v(true)
821        );
822        assert_eq!(
823            eval_str("(= (nth '(1 2 3) -1 :nf) :nf)").unwrap(),
824            bool_v(true)
825        );
826    }
827
828    // ── destructuring ─────────────────────────────────────────────────────
829
830    #[test]
831    fn test_sequential_destructure() {
832        assert_eq!(eval_str("(let* [[a b] [1 2]] (+ a b))").unwrap(), long(3));
833    }
834
835    #[test]
836    fn test_rest_destructure() {
837        let v = eval_str("(let* [[h & t] [1 2 3]] t)").unwrap();
838        assert!(matches!(v, Value::List(_)));
839        if let Value::List(l) = &v {
840            assert_eq!(l.get().count(), 2);
841        }
842    }
843
844    // ── defmacro ──────────────────────────────────────────────────────────
845
846    #[test]
847    fn test_defmacro() {
848        let (_, mut env) = make_env();
849        eval_src("(defmacro my-if [t a b] (list 'if t a b))", &mut env).unwrap();
850        assert_eq!(eval_src("(my-if true 1 2)", &mut env).unwrap(), long(1));
851        assert_eq!(eval_src("(my-if false 1 2)", &mut env).unwrap(), long(2));
852    }
853
854    // ── syntax-quote ──────────────────────────────────────────────────────
855
856    #[test]
857    fn test_syntax_quote_basic() {
858        let (_, mut env) = make_env();
859        eval_src("(def b 2)", &mut env).unwrap();
860        let v = eval_src("`(a ~b)", &mut env).unwrap();
861        assert!(matches!(v, Value::List(_)));
862        if let Value::List(l) = &v {
863            // Should be (user/a 2)
864            let items: Vec<_> = l.get().iter().cloned().collect();
865            assert_eq!(items.len(), 2);
866            assert_eq!(items[1], long(2));
867        }
868    }
869
870    // ── reader conditionals ───────────────────────────────────────────────
871
872    #[test]
873    fn test_reader_cond_rust() {
874        // :rust branch selected.
875        assert_eq!(eval_str("#?(:rust 1 :clj 2)").unwrap(), long(1));
876    }
877
878    #[test]
879    fn test_reader_cond_default() {
880        // No :rust; fall through to :default.
881        assert_eq!(eval_str("#?(:clj 2 :default 99)").unwrap(), long(99));
882    }
883
884    #[test]
885    fn test_reader_cond_splice_in_vector() {
886        assert_eq!(
887            eval_str("[1 #?@(:rust [:a :b]) 2]").unwrap(),
888            eval_str("[1 :a :b 2]").unwrap()
889        );
890    }
891
892    #[test]
893    fn test_reader_cond_splice_in_set() {
894        assert_eq!(
895            eval_str("#{1 #?@(:rust [2 3]) 4}").unwrap(),
896            eval_str("#{1 2 3 4}").unwrap()
897        );
898    }
899
900    #[test]
901    fn test_reader_cond_splice_in_map() {
902        assert_eq!(
903            eval_str("{:a 1 #?@(:rust [:b 2]) :c 3}").unwrap(),
904            eval_str("{:a 1 :b 2 :c 3}").unwrap()
905        );
906    }
907
908    #[test]
909    fn test_reader_cond_splice_in_call_args() {
910        assert_eq!(
911            eval_str("(vector 1 #?@(:rust [2 3]) 4)").unwrap(),
912            eval_str("[1 2 3 4]").unwrap()
913        );
914    }
915
916    #[test]
917    fn test_reader_cond_splice_no_match_removed() {
918        assert_eq!(
919            eval_str("[1 #?@(:clj [:a :b]) 2]").unwrap(),
920            eval_str("[1 2]").unwrap()
921        );
922    }
923
924    // These properties build ONE env per case and evaluate every spelling in
925    // it. `eval_str` bootstraps a whole runtime per call, and a property that
926    // evaluates a dozen spellings across 48 cases pays that hundreds of times.
927    // Nothing here defines anything, so one env per case is equivalent.
928    proptest::proptest! {
929        #![proptest_config(proptest::prelude::ProptestConfig::with_cases(48))]
930        /// Splicing `#?@(:rust mid)` into any container evaluates to the same
931        /// value as inlining `mid` literally - for vector, set, data-list, and
932        /// call arguments. Elements are distinct so set literals stay legal.
933        #[test]
934        fn prop_splice_evals_as_inline_in_every_container(
935            np in 0usize..3, nm in 0usize..3, ns in 0usize..3,
936        ) {
937            let kw_run = |start: usize, n: usize| {
938                (start..start + n)
939                    .map(|i| format!(":v{i}"))
940                    .collect::<Vec<_>>()
941                    .join(" ")
942            };
943            let p = kw_run(0, np);
944            let m = kw_run(np, nm);
945            let s = kw_run(np + nm, ns);
946            let (_g, mut env) = make_env();
947            for (open, close, quote) in
948                [("[", "]", ""), ("#{", "}", ""), ("(", ")", "'"), ("(vector ", ")", "")]
949            {
950                let spliced = format!("{quote}{open}{p} #?@(:rust [{m}]) {s}{close}");
951                let inlined = format!("{quote}{open}{p} {m} {s}{close}");
952                proptest::prop_assert_eq!(
953                    eval_src(&spliced, &mut env).unwrap(),
954                    eval_src(&inlined, &mut env).unwrap(),
955                    "container {}{}",
956                    open,
957                    close
958                );
959            }
960        }
961    }
962
963    // ── Reader conditionals: cross-path and binding-vector properties ─────
964    //
965    // One generated position in a container or binding vector. Names are
966    // assigned by index, so generated elements are distinct and set literals
967    // stay legal.
968
969    #[derive(Clone, Debug)]
970    enum Slot {
971        /// A plain element; contributes 1.
972        Plain,
973        /// `#?(…)`; contributes 1 when the branch key matches, else 0.
974        NonSplice { matches: bool },
975        /// `#?@(…)`; contributes `n` when the branch key matches, else 0.
976        Splice { matches: bool, n: usize },
977    }
978
979    fn slot_strat() -> impl proptest::strategy::Strategy<Value = Slot> {
980        use proptest::strategy::{Just, Strategy};
981        proptest::prop_oneof![
982            Just(Slot::Plain),
983            proptest::bool::ANY.prop_map(|matches| Slot::NonSplice { matches }),
984            (proptest::bool::ANY, 0usize..3).prop_map(|(matches, n)| Slot::Splice { matches, n }),
985        ]
986    }
987
988    /// Render `slots` twice - conditionals written out, and branches inlined -
989    /// with `unit` forms per contributed position. `unit(i)` renders element
990    /// `i`; a unit of two tokens (`x0 0`) keeps pair parity even, which is what
991    /// binding vectors need.
992    fn render_slots(slots: &[Slot], unit: &dyn Fn(usize) -> String) -> (String, String) {
993        let mut next = 0usize;
994        let (mut written, mut inlined) = (Vec::new(), Vec::new());
995        for slot in slots {
996            match *slot {
997                Slot::Plain => {
998                    let u = unit(next);
999                    next += 1;
1000                    written.push(u.clone());
1001                    inlined.push(u);
1002                }
1003                Slot::NonSplice { matches } => {
1004                    let u = unit(next);
1005                    next += 1;
1006                    let key = if matches { "rust" } else { "clj" };
1007                    written.push(format!("#?(:{key} {u})"));
1008                    if matches {
1009                        inlined.push(u);
1010                    }
1011                }
1012                Slot::Splice { matches, n } => {
1013                    let us: Vec<String> = (0..n)
1014                        .map(|_| {
1015                            let u = unit(next);
1016                            next += 1;
1017                            u
1018                        })
1019                        .collect();
1020                    let key = if matches { "rust" } else { "clj" };
1021                    written.push(format!("#?@(:{key} [{}])", us.join(" ")));
1022                    if matches {
1023                        inlined.extend(us);
1024                    }
1025                }
1026            }
1027        }
1028        (written.join(" "), inlined.join(" "))
1029    }
1030
1031    /// Binding vectors take pairs, and a non-splicing `#?` selects exactly one
1032    /// form - so it cannot carry a `name init` pair. Only plain positions and
1033    /// `#?@` splices can appear there.
1034    fn pair_slot_strat() -> impl proptest::strategy::Strategy<Value = Slot> {
1035        use proptest::strategy::{Just, Strategy};
1036        proptest::prop_oneof![
1037            Just(Slot::Plain),
1038            (proptest::bool::ANY, 0usize..3).prop_map(|(matches, n)| Slot::Splice { matches, n }),
1039        ]
1040    }
1041
1042    fn kw_unit(i: usize) -> String {
1043        format!(":k{i}")
1044    }
1045
1046    /// `x<i> <i>` - one binding pair, so every contributed position keeps the
1047    /// vector's parity even.
1048    fn binding_unit(i: usize) -> String {
1049        format!("x{i} {i}")
1050    }
1051
1052    proptest::proptest! {
1053        #![proptest_config(proptest::prelude::ProptestConfig::with_cases(48))]
1054
1055        /// A form's meaning must not depend on which path reads it. For the same
1056        /// container, evaluating it, quoting it, and syntax-quoting it all agree
1057        /// with the hand-inlined spelling. Before this fix the quoted and
1058        /// syntax-quoted paths dropped or nil-filled splices that the evaluated
1059        /// path expanded.
1060        #[test]
1061        fn prop_splice_agrees_across_eval_quote_and_syntax_quote(
1062            slots in proptest::collection::vec(slot_strat(), 0..5),
1063        ) {
1064            let (written, inlined) = render_slots(&slots, &kw_unit);
1065            let even = inlined.split_whitespace().count().is_multiple_of(2);
1066            let (_g, mut env) = make_env();
1067            for (open, close) in [("[", "]"), ("#{", "}"), ("{", "}")] {
1068                if open == "{" && !even {
1069                    continue;
1070                }
1071                for prefix in ["", "'", "`"] {
1072                    let got = eval_src(&format!("{prefix}{open}{written}{close}"), &mut env);
1073                    let want = eval_src(&format!("{prefix}{open}{inlined}{close}"), &mut env);
1074                    proptest::prop_assert_eq!(
1075                        got.map_err(|e| e.to_string()),
1076                        want.map_err(|e| e.to_string()),
1077                        "prefix {:?} container {}{}", prefix, open, close
1078                    );
1079                }
1080            }
1081            // Data lists have no evaluated spelling; check both quoted forms.
1082            for prefix in ["'", "`"] {
1083                let got = eval_src(&format!("{prefix}({written})"), &mut env);
1084                let want = eval_src(&format!("{prefix}({inlined})"), &mut env);
1085                proptest::prop_assert_eq!(
1086                    got.map_err(|e| e.to_string()),
1087                    want.map_err(|e| e.to_string()),
1088                    "prefix {:?} list", prefix
1089                );
1090            }
1091        }
1092
1093        /// Every binding vector resolves conditionals: `let*`, `loop*` and
1094        /// `binding` bind exactly what the inlined spelling binds.
1095        #[test]
1096        fn prop_splice_in_binding_vectors_equals_inline(
1097            slots in proptest::collection::vec(pair_slot_strat(), 0..4),
1098        ) {
1099            let (written, inlined) = render_slots(&slots, &binding_unit);
1100            let names: Vec<String> = inlined
1101                .split_whitespace()
1102                .step_by(2)
1103                .map(str::to_string)
1104                .collect();
1105            let body = format!("[{}]", names.join(" "));
1106            let (_g, mut env) = make_env();
1107            for head in ["let*", "loop*"] {
1108                let got = eval_src(&format!("({head} [{written}] {body})"), &mut env);
1109                let want = eval_src(&format!("({head} [{inlined}] {body})"), &mut env);
1110                proptest::prop_assert_eq!(
1111                    got.map_err(|e| e.to_string()),
1112                    want.map_err(|e| e.to_string()),
1113                    "{}", head
1114                );
1115            }
1116        }
1117    }
1118
1119    #[test]
1120    fn quoted_map_splice_lands_in_key_value_positions() {
1121        assert_eq!(
1122            eval_str("'{:a 1 #?@(:rust [:b 2]) :c 3}").unwrap(),
1123            eval_str("{:a 1 :b 2 :c 3}").unwrap()
1124        );
1125    }
1126
1127    #[test]
1128    fn syntax_quoted_splice_is_expanded_not_nil() {
1129        assert_eq!(
1130            eval_str("`(1 #?@(:rust [2 3]) 4)").unwrap(),
1131            eval_str("'(1 2 3 4)").unwrap()
1132        );
1133    }
1134
1135    #[test]
1136    fn map_literal_with_only_an_unmatched_conditional_reads_as_empty() {
1137        // The written parity is odd, the expansion is empty - the reader must
1138        // defer rather than reject.
1139        assert_eq!(eval_str("{#?(:clj :a)}").unwrap(), eval_str("{}").unwrap());
1140    }
1141
1142    #[test]
1143    fn loop_binding_vector_expands_splices() {
1144        assert_eq!(eval_str("(loop* [#?@(:rust [x 1])] x)").unwrap(), long(1));
1145    }
1146
1147    // ── Error cases ───────────────────────────────────────────────────────
1148
1149    #[test]
1150    fn test_unbound_symbol() {
1151        let r = eval_str("undefined-var-xyz");
1152        assert!(matches!(r, Err(EvalError::UnboundSymbol(_))));
1153    }
1154
1155    #[test]
1156    fn test_wrong_arity() {
1157        let (_, mut env) = make_env();
1158        eval_src("(defn one-arg [x] x)", &mut env).unwrap();
1159        let r = eval_src("(one-arg 1 2)", &mut env);
1160        assert!(matches!(r, Err(EvalError::Arity { .. })));
1161    }
1162
1163    #[test]
1164    fn test_not_callable() {
1165        let r = eval_str("(42 1 2)");
1166        assert!(matches!(r, Err(EvalError::NotCallable(_))));
1167    }
1168
1169    // ── Higher-order functions (bootstrap) ────────────────────────────────
1170
1171    #[test]
1172    fn test_map_fn() {
1173        assert_eq!(
1174            eval_str("(vec (map inc [1 2 3]))").unwrap(),
1175            eval_str("[2 3 4]").unwrap()
1176        );
1177    }
1178
1179    #[test]
1180    fn test_filter_fn() {
1181        assert_eq!(
1182            eval_str("(vec (filter odd? [1 2 3 4 5]))").unwrap(),
1183            eval_str("[1 3 5]").unwrap()
1184        );
1185    }
1186
1187    #[test]
1188    fn test_reduce_fn() {
1189        assert_eq!(eval_str("(reduce + [1 2 3 4 5])").unwrap(), long(15));
1190    }
1191
1192    #[test]
1193    fn test_apply_fn() {
1194        assert_eq!(eval_str("(apply + [1 2 3])").unwrap(), long(6));
1195    }
1196
1197    #[test]
1198    fn test_atom_ops() {
1199        let (_, mut env) = make_env();
1200        eval_src("(def a (atom 0))", &mut env).unwrap();
1201        eval_src("(swap! a inc)", &mut env).unwrap();
1202        assert_eq!(eval_src("(deref a)", &mut env).unwrap(), long(1));
1203    }
1204
1205    #[test]
1206    fn test_when_macro() {
1207        assert_eq!(eval_str("(when true 42)").unwrap(), long(42));
1208        assert_eq!(eval_str("(when false 42)").unwrap(), Value::Nil);
1209    }
1210
1211    #[test]
1212    fn test_cond_macro() {
1213        assert_eq!(eval_str("(cond false 1 true 2)").unwrap(), long(2));
1214    }
1215
1216    #[test]
1217    fn test_and_or() {
1218        assert_eq!(eval_str("(and 1 2 3)").unwrap(), long(3));
1219        assert_eq!(eval_str("(and 1 false 3)").unwrap(), bool_v(false));
1220        assert_eq!(eval_str("(or false nil 42)").unwrap(), long(42));
1221        assert_eq!(eval_str("(or false nil)").unwrap(), Value::Nil);
1222    }
1223
1224    // ── Phase 5: Lazy sequences ───────────────────────────────────────────
1225
1226    #[test]
1227    fn test_lazy_range() {
1228        assert_eq!(
1229            eval_str("(= (into [] (take 5 (range))) [0 1 2 3 4])").unwrap(),
1230            bool_v(true)
1231        );
1232    }
1233
1234    #[test]
1235    fn test_lazy_range_bounded() {
1236        assert_eq!(
1237            eval_str("(= (into [] (range 3)) [0 1 2])").unwrap(),
1238            bool_v(true)
1239        );
1240    }
1241
1242    #[test]
1243    fn test_lazy_iterate() {
1244        assert_eq!(
1245            eval_str("(= (into [] (take 3 (iterate inc 0))) [0 1 2])").unwrap(),
1246            bool_v(true)
1247        );
1248    }
1249
1250    #[test]
1251    fn test_lazy_repeat() {
1252        assert_eq!(
1253            eval_str("(= (into [] (take 3 (repeat :x))) [:x :x :x])").unwrap(),
1254            bool_v(true)
1255        );
1256    }
1257
1258    #[test]
1259    fn test_lazy_cycle() {
1260        assert_eq!(
1261            eval_str("(= (into [] (take 5 (cycle [1 2]))) [1 2 1 2 1])").unwrap(),
1262            bool_v(true)
1263        );
1264    }
1265
1266    // ── Phase 5: Associative destructuring ───────────────────────────────
1267
1268    #[test]
1269    fn test_assoc_destructure() {
1270        assert_eq!(
1271            eval_str("(let [{:keys [a b]} {:a 1 :b 2}] (+ a b))").unwrap(),
1272            long(3)
1273        );
1274    }
1275
1276    #[test]
1277    fn test_assoc_destructure_or() {
1278        assert_eq!(
1279            eval_str("(let [{:keys [a b] :or {b 99}} {:a 1}] b)").unwrap(),
1280            long(99)
1281        );
1282    }
1283
1284    // ── Phase 5: letfn ───────────────────────────────────────────────────
1285
1286    #[test]
1287    fn test_letfn() {
1288        assert_eq!(
1289            eval_str("(letfn [(fact [n] (if (= n 0) 1 (* n (fact (dec n)))))] (fact 5))").unwrap(),
1290            long(120)
1291        );
1292    }
1293
1294    // letfn scope is MUTUAL, not sequential: every binding is visible to every
1295    // other one regardless of order. These pin the three-pass construction in
1296    // `eval_letfn` — a closure captures values rather than cells, so without the
1297    // pass-3 back-patch a forward reference silently sees the nil placeholder
1298    // pass 1 left behind.
1299
1300    #[test]
1301    fn test_letfn_forward_reference() {
1302        assert_eq!(
1303            eval_str("(letfn [(f [n] (g n)) (g [n] (* n 2))] (f 21))").unwrap(),
1304            long(42)
1305        );
1306    }
1307
1308    #[test]
1309    fn test_letfn_mutual_recursion() {
1310        // `my-even?` names `my-odd?` before it is built — the direction that was
1311        // broken — and `my-odd?` names `my-even?` backwards.
1312        let defs = "(letfn [(my-even? [n] (if (= n 0) true (my-odd? (dec n))))
1313                            (my-odd? [n] (if (= n 0) false (my-even? (dec n))))]";
1314        assert_eq!(
1315            eval_str(&format!("{defs} (my-even? 10))")).unwrap(),
1316            Value::Bool(true)
1317        );
1318        assert_eq!(
1319            eval_str(&format!("{defs} (my-odd? 10))")).unwrap(),
1320            Value::Bool(false)
1321        );
1322    }
1323
1324    #[test]
1325    fn test_letfn_three_way_mutual_recursion() {
1326        let defs = "(letfn [(a [n] (if (= n 0) 1 (b (dec n))))
1327                            (b [n] (if (= n 0) 2 (c (dec n))))
1328                            (c [n] (if (= n 0) 3 (a (dec n))))]";
1329        for (arg, want) in [(0, 1), (1, 2), (2, 3), (3, 1)] {
1330            assert_eq!(
1331                eval_str(&format!("{defs} (a {arg}))")).unwrap(),
1332                long(want),
1333                "(a {arg})"
1334            );
1335        }
1336    }
1337
1338    #[test]
1339    fn test_letfn_closes_over_outer_local() {
1340        assert_eq!(
1341            eval_str("(let [start 10] (letfn [(step [n] (+ n start))] (step 5)))").unwrap(),
1342            long(15)
1343        );
1344    }
1345
1346    #[test]
1347    fn test_letfn_shadows_enclosing_binding() {
1348        // The letfn binding wins over the `let` of the same name, and siblings
1349        // that name it see the fn, not the shadowed value.
1350        assert_eq!(
1351            eval_str("(let [f 1] (letfn [(g [] (f)) (f [] 7)] (g)))").unwrap(),
1352            long(7)
1353        );
1354    }
1355
1356    #[test]
1357    fn test_letfn_binding_name_must_be_a_symbol() {
1358        assert!(eval_str("(letfn [(42 [n] n)] 1)").is_err());
1359    }
1360
1361    // ── Phase 5: namespace ops ────────────────────────────────────────────
1362
1363    #[test]
1364    fn test_in_ns() {
1365        let (_, mut env) = make_env();
1366        eval_src("(in-ns 'mytest)", &mut env).unwrap();
1367        assert_eq!(env.current_ns.as_ref(), "mytest");
1368        eval_src("(in-ns 'user)", &mut env).unwrap();
1369        assert_eq!(env.current_ns.as_ref(), "user");
1370    }
1371
1372    // ── Phase 5: spit / slurp ─────────────────────────────────────────────
1373
1374    #[test]
1375    fn test_spit_slurp() {
1376        let path = std::env::temp_dir().join("cljrs_test_spit_slurp.txt");
1377        let path_str = path.to_str().unwrap();
1378        let src = format!(
1379            r#"(do (spit "{}" "hello clojurust") (slurp "{}"))"#,
1380            path_str, path_str
1381        );
1382        let result = eval_str(&src).unwrap();
1383        if let Value::Str(s) = result {
1384            assert_eq!(s.get().as_str(), "hello clojurust");
1385        } else {
1386            panic!("expected string result from slurp");
1387        }
1388        let _ = std::fs::remove_file(path);
1389    }
1390
1391    // ── Phase 5: update-in ───────────────────────────────────────────────
1392
1393    #[test]
1394    fn test_update_in() {
1395        assert_eq!(
1396            eval_str("(= (update-in {:a {:b 1}} [:a :b] inc) {:a {:b 2}})").unwrap(),
1397            bool_v(true)
1398        );
1399    }
1400
1401    // ── Phase 5: if-let / when-let ────────────────────────────────────────
1402
1403    #[test]
1404    fn test_if_let_truthy() {
1405        assert_eq!(eval_str("(if-let [x 42] x :nope)").unwrap(), long(42));
1406    }
1407
1408    #[test]
1409    fn test_if_let_falsy() {
1410        assert_eq!(
1411            eval_str("(if-let [x nil] x :nope)").unwrap(),
1412            eval_str(":nope").unwrap()
1413        );
1414    }
1415
1416    #[test]
1417    fn test_when_let_truthy() {
1418        assert_eq!(eval_str("(when-let [x 7] (* x 2))").unwrap(), long(14));
1419    }
1420
1421    #[test]
1422    fn test_when_let_falsy() {
1423        assert_eq!(eval_str("(when-let [x nil] 99)").unwrap(), Value::Nil);
1424    }
1425
1426    // ── Phase 5: math functions ───────────────────────────────────────────
1427
1428    #[test]
1429    fn test_math_trig() {
1430        // sin(0) = 0, cos(0) = 1
1431        assert_eq!(eval_str("(Math/sin 0)").unwrap(), Value::Double(0.0));
1432        assert_eq!(eval_str("(Math/cos 0)").unwrap(), Value::Double(1.0));
1433    }
1434
1435    #[test]
1436    fn test_math_constants() {
1437        assert!(
1438            matches!(eval_str("Math/PI").unwrap(), Value::Double(v) if (v - std::f64::consts::PI).abs() < 1e-10)
1439        );
1440        assert!(
1441            matches!(eval_str("Math/E").unwrap(), Value::Double(v) if (v - std::f64::consts::E).abs() < 1e-10)
1442        );
1443    }
1444
1445    #[test]
1446    fn test_math_log_exp() {
1447        // exp(0) = 1, log(1) = 0
1448        assert_eq!(eval_str("(Math/exp 0)").unwrap(), Value::Double(1.0));
1449        assert_eq!(eval_str("(Math/log 1)").unwrap(), Value::Double(0.0));
1450    }
1451
1452    // ── Phase 6: Protocols & Multimethods ─────────────────────────────────
1453
1454    #[test]
1455    fn test_defprotocol() {
1456        // Defining a protocol creates a callable ProtocolFn that errors without impl.
1457        let result = eval_str(
1458            r#"
1459            (defprotocol Greet
1460              (greet [this]))
1461            (greet "hello")
1462            "#,
1463        );
1464        assert!(result.is_err());
1465        let msg = result.unwrap_err().to_string();
1466        assert!(msg.contains("No implementation"), "got: {msg}");
1467    }
1468
1469    #[test]
1470    fn test_extend_type() {
1471        let result = eval_str(
1472            r#"
1473            (defprotocol Greet
1474              (greet [this]))
1475            (extend-type String
1476              Greet
1477              (greet [this] (str "Hello, " this "!")))
1478            (greet "world")
1479            "#,
1480        )
1481        .unwrap();
1482        assert_eq!(result, Value::string("Hello, world!"));
1483    }
1484
1485    #[test]
1486    fn test_protocol_dispatch() {
1487        let result = eval_str(
1488            r#"
1489            (defprotocol Describable
1490              (describe [this]))
1491            (extend-type String
1492              Describable
1493              (describe [this] (str "string:" this)))
1494            (extend-type Long
1495              Describable
1496              (describe [this] (str "long:" this)))
1497            [(describe "hi") (describe 42)]
1498            "#,
1499        )
1500        .unwrap();
1501        assert!(matches!(result, Value::Vector(_)));
1502        let s = format!("{}", result);
1503        assert!(s.contains("string:hi"), "got: {s}");
1504        assert!(s.contains("long:42"), "got: {s}");
1505    }
1506
1507    #[test]
1508    fn test_extend_protocol() {
1509        let result = eval_str(
1510            r#"
1511            (defprotocol Showable
1512              (show [this]))
1513            (extend-protocol Showable
1514              String
1515              (show [this] (str "S:" this))
1516              Long
1517              (show [this] (str "L:" this)))
1518            [(show "x") (show 7)]
1519            "#,
1520        )
1521        .unwrap();
1522        let s = format!("{}", result);
1523        assert!(s.contains("S:x"), "got: {s}");
1524        assert!(s.contains("L:7"), "got: {s}");
1525    }
1526
1527    #[test]
1528    fn test_extend_via_metadata() {
1529        // `:extend-via-metadata true` lets an instance implement a protocol by
1530        // carrying the impl fn in its own metadata, keyed by the protocol
1531        // method's fully-qualified symbol (matching real Clojure's
1532        // `MethodImplCache` dispatch, which looks up `(.sym cache)` in
1533        // `(meta x)`) — no `extend-type`/`extend-protocol` needed. Idiomatic
1534        // usage produces that qualified symbol via syntax-quote.
1535        let result = eval_str(
1536            r#"
1537            (defprotocol IRender
1538              :extend-via-metadata true
1539              (create-element [this tag-name]))
1540            (def renderer (with-meta {} {`create-element (fn [this tag-name] (str "made-" tag-name))}))
1541            (create-element renderer "div")
1542            "#,
1543        )
1544        .unwrap();
1545        assert_eq!(result, Value::string("made-div"));
1546    }
1547
1548    #[test]
1549    fn test_extend_via_metadata_falls_back_to_type_tag() {
1550        // Metadata impls take priority, but a value without metadata still
1551        // dispatches on its type tag as usual.
1552        let result = eval_str(
1553            r#"
1554            (defprotocol IRender
1555              :extend-via-metadata true
1556              (create-element [this tag-name]))
1557            (extend-type Map
1558              IRender
1559              (create-element [this tag-name] (str "type-tag-" tag-name)))
1560            [(create-element {} "span")
1561             (create-element (with-meta {} {`create-element (fn [this tag-name] (str "meta-" tag-name))}) "div")]
1562            "#,
1563        )
1564        .unwrap();
1565        let s = format!("{}", result);
1566        assert!(s.contains("type-tag-span"), "got: {s}");
1567        assert!(s.contains("meta-div"), "got: {s}");
1568    }
1569
1570    #[test]
1571    fn test_extend_via_metadata_cross_ns() {
1572        // Mirrors Replicant's mutation_log fake renderer: `IRender` is
1573        // defined in `replicant.core`, and a test namespace `:refer`s the
1574        // method and implements it purely via metadata (no `extend-type`).
1575        // Syntax-quoting `create-element` there must resolve to the
1576        // protocol's home namespace (`replicant.core/create-element`), which
1577        // is exactly the key the dispatcher looks up.
1578        let dir = temp_ns_dir("extend_via_metadata_cross_ns");
1579        std::fs::create_dir_all(dir.join("replicant")).unwrap();
1580        std::fs::write(
1581            dir.join("replicant").join("core.cljrs"),
1582            r#"(ns replicant.core)
1583               (defprotocol IRender
1584                 :extend-via-metadata true
1585                 (create-element [this tag-name]))"#,
1586        )
1587        .unwrap();
1588        let (_, mut env) = make_env_with_paths(vec![dir]);
1589        let result = eval_src(
1590            r#"
1591            (ns mutation-log-test
1592              (:require [replicant.core :refer [create-element]]))
1593            (def renderer (with-meta {} {`create-element (fn [this tag-name] (str "made-" tag-name))}))
1594            (create-element renderer "div")
1595            "#,
1596            &mut env,
1597        )
1598        .unwrap();
1599        assert_eq!(result, Value::string("made-div"));
1600    }
1601
1602    #[test]
1603    fn test_extend_via_metadata_cross_ns_via_alias() {
1604        // The `:refer` case above never touches the buggy path: a `:refer`d
1605        // bare symbol resolves through `lookup_var_in_ns`, which was always
1606        // correct. Real usage syntax-quotes an *aliased* symbol instead —
1607        // `` `p/attached? `` — which used to hit `qualify_symbol`'s "already
1608        // has a slash, keep as-is" branch and leak the alias text (`p/...`)
1609        // into the produced symbol instead of resolving it to the protocol's
1610        // home namespace (`replicant.protocols/...`), so the metadata key
1611        // never matched.
1612        let dir = temp_ns_dir("extend_via_metadata_cross_ns_via_alias");
1613        std::fs::create_dir_all(dir.join("replicant")).unwrap();
1614        std::fs::write(
1615            dir.join("replicant").join("protocols.cljrs"),
1616            r#"(ns replicant.protocols)
1617               (defprotocol IRender
1618                 :extend-via-metadata true
1619                 (attached? [this el]))"#,
1620        )
1621        .unwrap();
1622        let (_, mut env) = make_env_with_paths(vec![dir]);
1623        let result = eval_src(
1624            r#"
1625            (ns mutation-log-test
1626              (:require [replicant.protocols :as p]))
1627            (def r (with-meta {:log []} {`p/attached? (fn [_ el] el)}))
1628            (p/attached? r :el)
1629            "#,
1630            &mut env,
1631        )
1632        .unwrap();
1633        assert_eq!(result, Value::keyword(Keyword::simple("el")));
1634    }
1635
1636    #[test]
1637    fn test_satisfies() {
1638        let result = eval_str(
1639            r#"
1640            (defprotocol Animal
1641              (speak [this]))
1642            (extend-type String
1643              Animal
1644              (speak [this] this))
1645            [(satisfies? Animal "dog") (satisfies? Animal 42)]
1646            "#,
1647        )
1648        .unwrap();
1649        let s = format!("{}", result);
1650        assert!(s.contains("true"), "got: {s}");
1651        assert!(s.contains("false"), "got: {s}");
1652    }
1653
1654    #[test]
1655    fn test_defmulti_defmethod() {
1656        // Note: fn param destructuring not yet supported; use explicit map lookups.
1657        let result = eval_str(
1658            r#"
1659            (defmulti area :shape)
1660            (defmethod area :circle [m] (* 3 (:r m) (:r m)))
1661            (defmethod area :rectangle [m] (* (:w m) (:h m)))
1662            [(area {:shape :circle :r 2}) (area {:shape :rectangle :w 3 :h 4})]
1663            "#,
1664        )
1665        .unwrap();
1666        let s = format!("{}", result);
1667        // circle: 3*2*2=12, rectangle: 3*4=12
1668        assert!(s.contains("12"), "got: {s}");
1669    }
1670
1671    #[test]
1672    fn test_default_dispatch() {
1673        let result = eval_str(
1674            r#"
1675            (defmulti classify :kind)
1676            (defmethod classify :default [x] :unknown)
1677            (defmethod classify :cat [x] :meow)
1678            [(classify {:kind :dog}) (classify {:kind :cat})]
1679            "#,
1680        )
1681        .unwrap();
1682        let s = format!("{}", result);
1683        assert!(s.contains(":unknown"), "got: {s}");
1684        assert!(s.contains(":meow"), "got: {s}");
1685    }
1686
1687    #[test]
1688    fn test_prefer_method() {
1689        // prefer-method shouldn't error; just records preference
1690        let result = eval_str(
1691            r#"
1692            (defmulti foo identity)
1693            (defmethod foo :a [x] 1)
1694            (prefer-method foo :a :b)
1695            (foo :a)
1696            "#,
1697        )
1698        .unwrap();
1699        assert_eq!(result, Value::Long(1));
1700    }
1701
1702    #[test]
1703    fn test_remove_method() {
1704        let result = eval_str(
1705            r#"
1706            (defmulti bar identity)
1707            (defmethod bar :x [_] 99)
1708            (remove-method bar :x)
1709            (bar :x)
1710            "#,
1711        );
1712        assert!(result.is_err());
1713        let msg = result.unwrap_err().to_string();
1714        assert!(msg.contains("No method"), "got: {msg}");
1715    }
1716
1717    // ── Phase 7: Concurrency primitives ──────────────────────────────────────
1718
1719    #[test]
1720    fn test_compare_and_set() {
1721        let result = eval_str(
1722            r#"
1723            (let [a (atom 10)]
1724              [(compare-and-set! a 10 20)   ; succeeds: 10 == 10
1725               (compare-and-set! a 10 30)   ; fails:    20 != 10
1726               @a])
1727            "#,
1728        )
1729        .unwrap();
1730        let s = format!("{}", result);
1731        assert!(s.contains("true"), "got: {s}");
1732        assert!(s.contains("false"), "got: {s}");
1733        assert!(s.contains("20"), "got: {s}");
1734    }
1735
1736    #[test]
1737    fn test_volatile() {
1738        let result = eval_str(
1739            r#"
1740            (let [v (volatile! 1)]
1741              (vreset! v 2)
1742              (vswap! v + 10)
1743              @v)
1744            "#,
1745        )
1746        .unwrap();
1747        assert_eq!(result, Value::Long(12));
1748    }
1749
1750    #[test]
1751    fn test_delay() {
1752        // Body should not be evaluated until forced.
1753        let result = eval_str(
1754            r#"
1755            (let [calls (atom 0)
1756                  d (delay (swap! calls inc) 42)]
1757              [@calls (force d) @calls (force d) @calls])
1758            "#,
1759        )
1760        .unwrap();
1761        let s = format!("{}", result);
1762        // calls starts at 0, force evaluates body once (returns 42), second force uses cache
1763        // s = [0 42 1 42 1]
1764        assert!(s.starts_with("[0 42 1 42 1]"), "got: {s}");
1765    }
1766
1767    #[test]
1768    fn test_realized() {
1769        let result = eval_str(
1770            r#"
1771            (let [d (delay 99)]
1772              [(realized? d) (force d) (realized? d)])
1773            "#,
1774        )
1775        .unwrap();
1776        let s = format!("{}", result);
1777        assert!(s.starts_with("[false 99 true]"), "got: {s}");
1778    }
1779
1780    #[test]
1781    fn test_promise() {
1782        let result = eval_str(
1783            r#"
1784            (let [p (promise)]
1785              (deliver p 42)
1786              (deliver p 99)  ; second deliver is ignored
1787              @p)
1788            "#,
1789        )
1790        .unwrap();
1791        assert_eq!(result, Value::Long(42));
1792    }
1793
1794    #[test]
1795    #[ignore = "clojure.core/future is undefined by design: @f deadlocks the one \
1796                thread the task needs (GcPtr: !Send). cljrs.core.experimental/future \
1797                is the cooperative stand-in, read with (await f)"]
1798    fn test_future() {
1799        let result = eval_str(
1800            r#"
1801            (let [f (future (+ 1 2))]
1802              @f)
1803            "#,
1804        )
1805        .unwrap();
1806        assert_eq!(result, Value::Long(3));
1807    }
1808
1809    #[test]
1810    #[ignore = "agent not yet implemented (Phase A1 — GcPtr: !Send)"]
1811    fn test_agent_send() {
1812        let result = eval_str(
1813            r#"
1814            (let [a (agent 0)]
1815              (send a + 1)
1816              (send a + 2)
1817              (await-agent a)
1818              @a)
1819            "#,
1820        )
1821        .unwrap();
1822        assert_eq!(result, Value::Long(3));
1823    }
1824
1825    #[test]
1826    #[ignore = "agent not yet implemented (Phase A1 — GcPtr: !Send)"]
1827    fn test_agent_error_restart() {
1828        let result = eval_str(
1829            r#"
1830            (let [a (agent 10)]
1831              (send a (fn [_] (throw (ex-info "boom" {}))))
1832              (await-agent a)
1833              (let [err (agent-error a)]
1834                (restart-agent a 99)
1835                [err @a]))
1836            "#,
1837        )
1838        .unwrap();
1839        let s = format!("{}", result);
1840        // err should be a string containing "boom", @a should be 99
1841        assert!(s.contains("boom"), "got: {s}");
1842        assert!(s.contains("99"), "got: {s}");
1843    }
1844
1845    #[test]
1846    fn test_defrecord_basic() {
1847        // Constructor and field access via keyword.
1848        let result = eval_str(
1849            r#"
1850            (defrecord Point [x y])
1851            (let [p (->Point 3 4)]
1852              [(:x p) (:y p)])
1853            "#,
1854        )
1855        .unwrap();
1856        assert_eq!(result.to_string(), "[3 4]");
1857    }
1858
1859    #[test]
1860    fn test_defrecord_map_constructor() {
1861        let result = eval_str(
1862            r#"
1863            (defrecord Color [r g b])
1864            (let [c (map->Color {:r 255 :g 128 :b 0})]
1865              [(:r c) (:g c) (:b c)])
1866            "#,
1867        )
1868        .unwrap();
1869        assert_eq!(result.to_string(), "[255 128 0]");
1870    }
1871
1872    #[test]
1873    fn test_defrecord_assoc() {
1874        // assoc on a record returns a new record of the same type.
1875        let result = eval_str(
1876            r#"
1877            (defrecord Pt [x y])
1878            (let [p (->Pt 1 2)
1879                  q (assoc p :x 99)]
1880              [(:x q) (:y q) (record? q)])
1881            "#,
1882        )
1883        .unwrap();
1884        assert_eq!(result.to_string(), "[99 2 true]");
1885    }
1886
1887    #[test]
1888    fn test_defrecord_with_protocol() {
1889        let result = eval_str(
1890            r#"
1891            (defprotocol IShape
1892              (area [this]))
1893            (defrecord Circle [radius]
1894              IShape
1895              (area [this] (* 3 (:radius this) (:radius this))))
1896            (let [c (->Circle 5)]
1897              (area c))
1898            "#,
1899        )
1900        .unwrap();
1901        assert_eq!(result, cljrs_value::Value::Long(75));
1902    }
1903
1904    #[test]
1905    fn test_instance_q() {
1906        let result = eval_str(
1907            r#"
1908            (defrecord Dog [name])
1909            (let [d (->Dog "Rex")]
1910              [(instance? Dog d) (instance? Dog 42)])
1911            "#,
1912        )
1913        .unwrap();
1914        assert_eq!(result.to_string(), "[true false]");
1915    }
1916
1917    #[test]
1918    fn test_reify_basic() {
1919        let result = eval_str(
1920            r#"
1921            (defprotocol IGreet
1922              (greet [this name]))
1923            (let [greeter (reify IGreet
1924                            (greet [this name] (str "Hello, " name "!")))]
1925              (greet greeter "World"))
1926            "#,
1927        )
1928        .unwrap();
1929        assert_eq!(result.to_string(), "\"Hello, World!\"");
1930    }
1931
1932    // ── require / load-file ───────────────────────────────────────────────
1933
1934    fn temp_ns_dir(test_name: &str) -> std::path::PathBuf {
1935        let dir = std::env::temp_dir().join(format!("cljrs_test_{test_name}"));
1936        let _ = std::fs::remove_dir_all(&dir);
1937        std::fs::create_dir_all(&dir).unwrap();
1938        dir
1939    }
1940
1941    fn make_env_with_paths(paths: Vec<std::path::PathBuf>) -> (Arc<GlobalEnv>, Env) {
1942        let globals = crate::Runtime::builder()
1943            .execution_mode(crate::ExecutionMode::TreeWalk)
1944            .eager_clojure_test(true)
1945            .source_paths(paths)
1946            .build()
1947            .expect("runtime")
1948            .into_globals();
1949        let env = Env::new(globals.clone(), "user");
1950        (globals, env)
1951    }
1952
1953    #[test]
1954    fn test_require_as() {
1955        let dir = temp_ns_dir("require_as");
1956        std::fs::write(
1957            dir.join("mylib.cljrs"),
1958            "(ns mylib) (defn greet [n] (str \"hello \" n))",
1959        )
1960        .unwrap();
1961        let (_, mut env) = make_env_with_paths(vec![dir]);
1962        let result = eval_src("(require '[mylib :as ml]) (ml/greet \"world\")", &mut env).unwrap();
1963        assert_eq!(result.to_string(), "\"hello world\"");
1964    }
1965
1966    #[test]
1967    fn test_require_refer() {
1968        let dir = temp_ns_dir("require_refer");
1969        std::fs::write(
1970            dir.join("myutil.cljrs"),
1971            "(ns myutil) (defn twice [x] (* 2 x))",
1972        )
1973        .unwrap();
1974        let (_, mut env) = make_env_with_paths(vec![dir]);
1975        let result = eval_src("(require '[myutil :refer [twice]]) (twice 21)", &mut env).unwrap();
1976        assert_eq!(result, Value::Long(42));
1977    }
1978
1979    #[test]
1980    fn test_require_refer_all() {
1981        let dir = temp_ns_dir("require_refer_all");
1982        std::fs::write(
1983            dir.join("mymath.cljrs"),
1984            "(ns mymath) (defn square [x] (* x x))",
1985        )
1986        .unwrap();
1987        let (_, mut env) = make_env_with_paths(vec![dir]);
1988        let result = eval_src("(require '[mymath :refer :all]) (square 7)", &mut env).unwrap();
1989        assert_eq!(result, Value::Long(49));
1990    }
1991
1992    #[test]
1993    fn test_ns_require_clause() {
1994        let dir = temp_ns_dir("ns_require");
1995        std::fs::write(
1996            dir.join("greeter.cljrs"),
1997            "(ns greeter) (defn hi [n] (str \"Hi \" n))",
1998        )
1999        .unwrap();
2000        let (_, mut env) = make_env_with_paths(vec![dir]);
2001        let result = eval_src(
2002            "(ns myapp (:require [greeter :as g])) (g/hi \"Alice\")",
2003            &mut env,
2004        )
2005        .unwrap();
2006        assert_eq!(result.to_string(), "\"Hi Alice\"");
2007    }
2008
2009    #[test]
2010    fn test_var_quote_alias_resolution() {
2011        // #'alias/sym must resolve the alias to the full namespace, just like
2012        // a regular function call does (issue #187).
2013        let dir = temp_ns_dir("var_quote_alias");
2014        // lib.core maps to lib/core.cljrs on the source path.
2015        std::fs::create_dir_all(dir.join("lib")).unwrap();
2016        std::fs::write(
2017            dir.join("lib/core.cljrs"),
2018            "(ns lib.core) (defn public [x] (* x 2))",
2019        )
2020        .unwrap();
2021        let (_, mut env) = make_env_with_paths(vec![dir]);
2022        // Regular call via alias must work first.
2023        let call_result = eval_src("(require '[lib.core :as l]) (l/public 21)", &mut env).unwrap();
2024        assert_eq!(call_result, Value::Long(42));
2025        // #'alias/sym reader form.
2026        let var_result = eval_src("#'l/public", &mut env).unwrap();
2027        assert!(
2028            matches!(var_result, Value::Var(_)),
2029            "expected Var, got {var_result:?}"
2030        );
2031        assert_eq!(var_result.to_string(), "#'lib.core/public");
2032        // (var alias/sym) special form must also resolve the alias.
2033        let var_special = eval_src("(var l/public)", &mut env).unwrap();
2034        assert_eq!(var_special.to_string(), "#'lib.core/public");
2035    }
2036
2037    #[test]
2038    fn test_require_idempotent() {
2039        let dir = temp_ns_dir("require_idempotent");
2040        // File has a side effect tracked via an atom
2041        std::fs::write(
2042            dir.join("counter.cljrs"),
2043            "(ns counter) (def loaded-count (atom 0)) (swap! loaded-count inc)",
2044        )
2045        .unwrap();
2046        let (globals, mut env) = make_env_with_paths(vec![dir]);
2047        eval_src("(require 'counter)", &mut env).unwrap();
2048        eval_src("(require 'counter)", &mut env).unwrap();
2049        // The atom should have been incremented only once.
2050        let count = globals.lookup_in_ns("counter", "loaded-count").unwrap();
2051        if let Value::Atom(a) = count {
2052            assert_eq!(a.get().deref(), Value::Long(1));
2053        } else {
2054            panic!("expected atom");
2055        }
2056    }
2057
2058    #[test]
2059    fn test_require_not_found() {
2060        let (_, mut env) = make_env_with_paths(vec![]);
2061        let err = eval_src("(require 'nonexistent.ns)", &mut env).unwrap_err();
2062        let msg = format!("{err:?}");
2063        assert!(msg.contains("nonexistent.ns"), "unexpected error: {msg}");
2064    }
2065
2066    #[test]
2067    fn test_require_circular() {
2068        let dir = temp_ns_dir("require_circular");
2069        // a requires b, b requires a
2070        std::fs::write(dir.join("cira.cljrs"), "(ns cira (:require [cirb]))").unwrap();
2071        std::fs::write(dir.join("cirb.cljrs"), "(ns cirb (:require [cira]))").unwrap();
2072        let (_, mut env) = make_env_with_paths(vec![dir]);
2073        let err = eval_src("(require 'cira)", &mut env).unwrap_err();
2074        let msg = format!("{err:?}");
2075        assert!(
2076            msg.contains("circular"),
2077            "expected circular error, got: {msg}"
2078        );
2079    }
2080
2081    #[test]
2082    fn test_load_file() {
2083        let dir = temp_ns_dir("load_file");
2084        let path = dir.join("script.cljrs");
2085        std::fs::write(&path, "(+ 1 2)").unwrap();
2086        let (_, mut env) = make_env_with_paths(vec![]);
2087        let result = eval_src(&format!("(load-file \"{}\")", path.display()), &mut env).unwrap();
2088        assert_eq!(result, Value::Long(3));
2089    }
2090
2091    // ── *ns* and namespace reflection ─────────────────────────────────────────
2092
2093    #[test]
2094    fn test_star_ns_initial() {
2095        // After standard_env(), *ns* should be the user namespace.
2096        let (_, mut env) = make_env();
2097        let v = eval_src("*ns*", &mut env).unwrap();
2098        match v {
2099            Value::Namespace(ns) => assert_eq!(ns.get().name.as_ref(), "user"),
2100            other => panic!("expected Namespace, got {other:?}"),
2101        }
2102    }
2103
2104    #[test]
2105    fn test_star_ns_after_in_ns() {
2106        let (_, mut env) = make_env();
2107        eval_src("(in-ns 'myns)", &mut env).unwrap();
2108        let v = eval_src("*ns*", &mut env).unwrap();
2109        match v {
2110            Value::Namespace(ns) => assert_eq!(ns.get().name.as_ref(), "myns"),
2111            other => panic!("expected Namespace, got {other:?}"),
2112        }
2113    }
2114
2115    #[test]
2116    fn test_star_ns_after_ns_form() {
2117        let (_, mut env) = make_env();
2118        eval_src("(ns mytest.ns)", &mut env).unwrap();
2119        let v = eval_src("*ns*", &mut env).unwrap();
2120        match v {
2121            Value::Namespace(ns) => assert_eq!(ns.get().name.as_ref(), "mytest.ns"),
2122            other => panic!("expected Namespace, got {other:?}"),
2123        }
2124    }
2125
2126    #[test]
2127    fn test_ns_name() {
2128        let (_, mut env) = make_env();
2129        let v = eval_src("(ns-name *ns*)", &mut env).unwrap();
2130        match v {
2131            Value::Symbol(s) => assert_eq!(s.get().name.as_ref(), "user"),
2132            other => panic!("expected Symbol, got {other:?}"),
2133        }
2134    }
2135
2136    #[test]
2137    fn test_find_ns() {
2138        let (_, mut env) = make_env();
2139        // known ns
2140        let v = eval_src("(find-ns 'user)", &mut env).unwrap();
2141        assert!(matches!(v, Value::Namespace(_)));
2142        // unknown ns
2143        let v2 = eval_src("(find-ns 'nonexistent)", &mut env).unwrap();
2144        assert_eq!(v2, Value::Nil);
2145    }
2146
2147    #[test]
2148    fn test_all_ns() {
2149        let (_, mut env) = make_env();
2150        let v = eval_src("(all-ns)", &mut env).unwrap();
2151        // Should be a list containing at least user and clojure.core
2152        let names: Vec<String> = match &v {
2153            Value::List(l) => l
2154                .get()
2155                .iter()
2156                .filter_map(|ns| match ns {
2157                    Value::Namespace(n) => Some(n.get().name.as_ref().to_string()),
2158                    _ => None,
2159                })
2160                .collect(),
2161            other => panic!("expected list, got {other:?}"),
2162        };
2163        assert!(names.contains(&"user".to_string()));
2164        assert!(names.contains(&"clojure.core".to_string()));
2165    }
2166
2167    #[test]
2168    fn test_ns_interns() {
2169        let (_, mut env) = make_env();
2170        eval_src("(def my-test-var 42)", &mut env).unwrap();
2171        let v = eval_src("(ns-interns *ns*)", &mut env).unwrap();
2172        let Value::Map(m) = v else {
2173            panic!("expected map")
2174        };
2175        // The map should contain 'my-test-var
2176        let sym = Value::symbol(cljrs_value::Symbol::simple("my-test-var"));
2177        assert!(m.get(&sym).is_some());
2178    }
2179
2180    #[test]
2181    fn test_create_ns() {
2182        let (_, mut env) = make_env();
2183        let v = eval_src("(create-ns 'fresh.ns)", &mut env).unwrap();
2184        match v {
2185            Value::Namespace(ns) => assert_eq!(ns.get().name.as_ref(), "fresh.ns"),
2186            other => panic!("expected Namespace, got {other:?}"),
2187        }
2188        // find-ns should now find it
2189        let v2 = eval_src("(find-ns 'fresh.ns)", &mut env).unwrap();
2190        assert!(matches!(v2, Value::Namespace(_)));
2191    }
2192
2193    // ── Dynamic variables (Phase 9) ───────────────────────────────────────────
2194
2195    #[test]
2196    fn test_dynamic_var_basic() {
2197        let (globals, mut env) = make_env();
2198        let result = eval_src("(def ^:dynamic *x* 10) (binding [*x* 42] *x*)", &mut env).unwrap();
2199        assert_eq!(result, Value::Long(42));
2200        // verify root is still bound
2201        let root = globals.lookup_in_ns("user", "*x*");
2202        assert_eq!(root, Some(Value::Long(10)));
2203    }
2204
2205    #[test]
2206    fn test_dynamic_var_restore() {
2207        let (_globals, mut env) = make_env();
2208        eval_src("(def ^:dynamic *x* 10)", &mut env).unwrap();
2209        eval_src("(binding [*x* 42] *x*)", &mut env).unwrap();
2210        // After binding block, value restored to root
2211        let val = eval_src("*x*", &mut env).unwrap();
2212        assert_eq!(val, Value::Long(10));
2213    }
2214
2215    #[test]
2216    fn test_dynamic_var_nested() {
2217        let (_, mut env) = make_env();
2218        eval_src("(def ^:dynamic *x* 1)", &mut env).unwrap();
2219        let result = eval_src("(binding [*x* 2] (binding [*x* 3] *x*))", &mut env).unwrap();
2220        assert_eq!(result, Value::Long(3));
2221        // After both blocks
2222        let val = eval_src("*x*", &mut env).unwrap();
2223        assert_eq!(val, Value::Long(1));
2224    }
2225
2226    #[test]
2227    fn test_dynamic_var_unaffected() {
2228        let (_, mut env) = make_env();
2229        eval_src("(def ^:dynamic *x* 10)", &mut env).unwrap();
2230        eval_src("(def y 99)", &mut env).unwrap();
2231        eval_src("(binding [*x* 42] *x*)", &mut env).unwrap();
2232        // non-dynamic var y is unchanged
2233        let val = eval_src("y", &mut env).unwrap();
2234        assert_eq!(val, Value::Long(99));
2235    }
2236
2237    #[test]
2238    #[ignore = "clojure.core/future is undefined by design; the experimental \
2239                stand-in does not convey dynamic bindings to its task"]
2240    fn test_binding_conveyance() {
2241        let (_, mut env) = make_env();
2242        eval_src("(def ^:dynamic *x* 10)", &mut env).unwrap();
2243        let result = eval_src("(binding [*x* 42] @(future *x*))", &mut env).unwrap();
2244        assert_eq!(result, Value::Long(42));
2245    }
2246
2247    #[test]
2248    fn test_var_set_in_binding() {
2249        let (_, mut env) = make_env();
2250        eval_src("(def ^:dynamic *x* 10)", &mut env).unwrap();
2251        // set! inside binding sets thread-local
2252        let inside = eval_src("(binding [*x* 1] (set! *x* 2) *x*)", &mut env).unwrap();
2253        assert_eq!(inside, Value::Long(2));
2254        // root still 10
2255        let root = eval_src("*x*", &mut env).unwrap();
2256        assert_eq!(root, Value::Long(10));
2257    }
2258
2259    #[test]
2260    fn test_with_bindings_star() {
2261        let (_, mut env) = make_env();
2262        eval_src("(def ^:dynamic *x* 10)", &mut env).unwrap();
2263        let result = eval_src("(with-bindings* {#'*x* 99} (fn [] *x*))", &mut env).unwrap();
2264        assert_eq!(result, Value::Long(99));
2265    }
2266
2267    #[test]
2268    fn test_binding_fully_qualified_cross_ns_dynamic_var() {
2269        let (_, mut env) = make_env();
2270        let result = eval_src(
2271            r#"
2272            (ns other.ns)
2273            (def ^:dynamic *dispatch* nil)
2274            (ns user)
2275            (binding [other.ns/*dispatch* (fn [x] x)]
2276              (other.ns/*dispatch* 42))
2277            "#,
2278            &mut env,
2279        )
2280        .unwrap();
2281        assert_eq!(result, Value::Long(42));
2282    }
2283
2284    #[test]
2285    fn test_binding_aliased_cross_ns_dynamic_var() {
2286        // (binding [alias/*var* v] ...) must resolve `alias` through the
2287        // current ns's `:require :as` aliases, exactly like ordinary
2288        // qualified-symbol lookup — this is how Replicant's public
2289        // `set-dispatch!`/life-cycle dispatch binds `*dispatch*` across
2290        // namespaces.
2291        let dir = temp_ns_dir("binding_aliased_cross_ns_dynamic_var");
2292        std::fs::create_dir_all(dir.join("replicant")).unwrap();
2293        std::fs::write(
2294            dir.join("replicant").join("core.cljrs"),
2295            r#"(ns replicant.core)
2296               (def ^:dynamic *dispatch* nil)
2297               (defn call-dispatch [x] (*dispatch* x))"#,
2298        )
2299        .unwrap();
2300        let (_, mut env) = make_env_with_paths(vec![dir]);
2301        let result = eval_src(
2302            r#"
2303            (ns life-cycle-test
2304              (:require [replicant.core :as r]))
2305            (binding [r/*dispatch* (fn [x] x)]
2306              (r/call-dispatch 42))
2307            "#,
2308            &mut env,
2309        )
2310        .unwrap();
2311        assert_eq!(result, Value::Long(42));
2312    }
2313
2314    #[test]
2315    fn test_meta_on_var() {
2316        let (_, mut env) = make_env();
2317        eval_src("(def ^:dynamic *x* 1)", &mut env).unwrap();
2318        let m = eval_src("(meta #'*x*)", &mut env).unwrap();
2319        // meta should be {:dynamic true}
2320        if let Value::Map(mv) = &m {
2321            let kw = Value::keyword(cljrs_value::Keyword::parse("dynamic"));
2322            assert_eq!(mv.get(&kw), Some(Value::Bool(true)));
2323        } else {
2324            panic!("expected map, got {m:?}");
2325        }
2326    }
2327
2328    #[test]
2329    fn test_bound_pred() {
2330        let (_, mut env) = make_env();
2331        eval_src("(def ^:dynamic *x* 1)", &mut env).unwrap();
2332        let t = eval_src("(bound? #'*x*)", &mut env).unwrap();
2333        assert_eq!(t, Value::Bool(true));
2334    }
2335
2336    #[test]
2337    fn test_alter_var_root() {
2338        let (_, mut env) = make_env();
2339        eval_src("(def x 1)", &mut env).unwrap();
2340        eval_src("(alter-var-root #'x inc)", &mut env).unwrap();
2341        let val = eval_src("x", &mut env).unwrap();
2342        assert_eq!(val, Value::Long(2));
2343    }
2344
2345    // ── clojure.test ─────────────────────────────────────────────────────────
2346
2347    #[test]
2348    fn test_clojure_test_is_pass() {
2349        // (is expr) returns true on a passing assertion.
2350        let (_, mut env) = make_env();
2351        eval_src(
2352            "(require '[clojure.test :refer [is deftest run-tests]])",
2353            &mut env,
2354        )
2355        .unwrap();
2356        let v = eval_src("(is (= 1 1))", &mut env).unwrap();
2357        assert_eq!(v, Value::Bool(true));
2358    }
2359
2360    #[test]
2361    fn test_clojure_test_is_fail() {
2362        // (is expr) returns false on a failing assertion.
2363        let (_, mut env) = make_env();
2364        eval_src("(require '[clojure.test :refer [is]])", &mut env).unwrap();
2365        let v = eval_src("(is (= 1 2))", &mut env).unwrap();
2366        assert_eq!(v, Value::Bool(false));
2367    }
2368
2369    #[test]
2370    fn test_clojure_test_is_catch_error() {
2371        // (is expr) catches runtime errors and returns false.
2372        let (_, mut env) = make_env();
2373        eval_src("(require '[clojure.test :refer [is]])", &mut env).unwrap();
2374        let v = eval_src("(is (/ 1 0))", &mut env).unwrap();
2375        assert_eq!(v, Value::Bool(false));
2376    }
2377
2378    #[test]
2379    fn test_clojure_test_deftest_and_run() {
2380        // deftest + run-tests smoke test: counters reflect pass/fail.
2381        let (_, mut env) = make_env();
2382        eval_src(
2383            "(require '[clojure.test :refer [deftest is run-tests]])",
2384            &mut env,
2385        )
2386        .unwrap();
2387        eval_src("(deftest my-passing-test (is (= 1 1)))", &mut env).unwrap();
2388        eval_src("(deftest my-failing-test (is (= 1 2)))", &mut env).unwrap();
2389        let counters = eval_src("(run-tests)", &mut env).unwrap();
2390        // Should have run 2 tests, 1 pass, 1 fail.
2391        if let Value::Map(m) = counters {
2392            let get = |k: &str| {
2393                m.get(&Value::keyword(cljrs_value::Keyword {
2394                    namespace: None,
2395                    name: Arc::from(k),
2396                }))
2397            };
2398            assert_eq!(get("test"), Some(Value::Long(2)));
2399            assert_eq!(get("pass"), Some(Value::Long(1)));
2400            assert_eq!(get("fail"), Some(Value::Long(1)));
2401            assert_eq!(get("error"), Some(Value::Long(0)));
2402        } else {
2403            panic!("expected map from run-tests, got {counters:?}");
2404        }
2405    }
2406
2407    #[test]
2408    fn test_alter_meta_bang() {
2409        // alter-meta! applies fn to var's meta and stores result.
2410        let (_, mut env) = make_env();
2411        eval_src("(def myvar 42)", &mut env).unwrap();
2412        eval_src("(alter-meta! #'myvar assoc :foo :bar)", &mut env).unwrap();
2413        let m = eval_src("(meta #'myvar)", &mut env).unwrap();
2414        if let Value::Map(map) = m {
2415            let foo_key = Value::keyword(cljrs_value::Keyword {
2416                namespace: None,
2417                name: Arc::from("foo"),
2418            });
2419            assert!(map.get(&foo_key).is_some());
2420        } else {
2421            panic!("expected map, got {m:?}");
2422        }
2423    }
2424
2425    #[test]
2426    fn test_catch_runtime_error() {
2427        // (try (/ 1 0) (catch Exception e "caught")) => "caught"
2428        let (_, mut env) = make_env();
2429        let v = eval_src(r#"(try (/ 1 0) (catch Exception e "caught"))"#, &mut env).unwrap();
2430        assert_eq!(v, Value::string("caught".to_string()));
2431    }
2432
2433    #[test]
2434    fn test_ns_resolve() {
2435        let (_, mut env) = make_env();
2436        eval_src("(def somevar 99)", &mut env).unwrap();
2437        // ns-resolve with current ns returns the var.
2438        let v = eval_src("(ns-resolve *ns* 'somevar)", &mut env).unwrap();
2439        assert!(matches!(v, Value::Var(_)));
2440        // ns-resolve for non-existent symbol returns nil.
2441        let v2 = eval_src("(ns-resolve *ns* 'nonexistent)", &mut env).unwrap();
2442        assert_eq!(v2, Value::Nil);
2443    }
2444
2445    // ── Persistent structure virtualization ──────────────────────────────
2446
2447    #[test]
2448    fn test_assoc_chain_virtualized() {
2449        // Assoc chain where intermediates aren't used — should be virtualized.
2450        let v = eval_str(
2451            "(let [m {}
2452                   a (assoc m :x 1)
2453                   b (assoc a :y 2)
2454                   c (assoc b :z 3)]
2455               c)",
2456        )
2457        .unwrap();
2458        // Result should be {:x 1, :y 2, :z 3}.
2459        assert!(matches!(&v, Value::Map(_)));
2460        if let Value::Map(m) = &v {
2461            assert_eq!(m.count(), 3);
2462            assert_eq!(m.get(&Value::keyword(Keyword::simple("x"))), Some(long(1)));
2463            assert_eq!(m.get(&Value::keyword(Keyword::simple("y"))), Some(long(2)));
2464            assert_eq!(m.get(&Value::keyword(Keyword::simple("z"))), Some(long(3)));
2465        }
2466    }
2467
2468    #[test]
2469    fn test_conj_chain_virtualized() {
2470        // Conj chain on a vector.
2471        let v = eval_str(
2472            "(let [v [1]
2473                   a (conj v 2)
2474                   b (conj a 3)
2475                   c (conj b 4)]
2476               c)",
2477        )
2478        .unwrap();
2479        assert_eq!(v, eval_str("[1 2 3 4]").unwrap());
2480    }
2481
2482    #[test]
2483    fn test_assoc_chain_intermediate_used_no_virtualize() {
2484        // If an intermediate is used in the body, virtualization should not apply,
2485        // but the result should still be correct.
2486        let v = eval_str(
2487            "(let [a (assoc {} :x 1)
2488                   b (assoc a :y 2)]
2489               (list (count a) (count b)))",
2490        )
2491        .unwrap();
2492        // a has 1 entry, b has 2.
2493        if let Value::List(l) = &v {
2494            let items: Vec<_> = l.get().iter().cloned().collect();
2495            assert_eq!(items, vec![long(1), long(2)]);
2496        } else {
2497            panic!("expected list, got {:?}", v);
2498        }
2499    }
2500
2501    #[test]
2502    fn test_assoc_chain_on_existing_map() {
2503        // Chain on an existing non-empty map.
2504        let v = eval_str(
2505            "(let [m {:a 1}
2506                   a (assoc m :b 2)
2507                   b (assoc a :c 3)]
2508               b)",
2509        )
2510        .unwrap();
2511        if let Value::Map(m) = &v {
2512            assert_eq!(m.count(), 3);
2513        } else {
2514            panic!("expected map");
2515        }
2516    }
2517
2518    // ── :pre/:post conditions ─────────────────────────────────────────────
2519
2520    #[test]
2521    fn test_post_condition_percent_bound() {
2522        // % must resolve to the return value inside :post conditions.
2523        let v = eval_str("(defn g [x] {:post [(pos? %)]} (inc x)) (g 5)").unwrap();
2524        assert_eq!(v, long(6));
2525    }
2526
2527    #[test]
2528    fn test_post_condition_violation_throws() {
2529        // A failing :post condition must throw.
2530        let r = eval_str("(defn g [x] {:post [(neg? %)]} (inc x)) (g 5)");
2531        assert!(r.is_err(), "expected error from failing :post condition");
2532    }
2533
2534    #[test]
2535    fn test_pre_condition_passes() {
2536        let v = eval_str("(defn g [x] {:pre [(pos? x)]} (inc x)) (g 5)").unwrap();
2537        assert_eq!(v, long(6));
2538    }
2539
2540    #[test]
2541    fn test_pre_condition_violation_throws() {
2542        let r = eval_str("(defn g [x] {:pre [(pos? x)]} (inc x)) (g -1)");
2543        assert!(r.is_err(), "expected error from failing :pre condition");
2544    }
2545
2546    #[test]
2547    fn test_pre_and_post_conditions() {
2548        let v = eval_str("(defn g [x] {:pre [(pos? x)] :post [(> % x)]} (inc x)) (g 3)").unwrap();
2549        assert_eq!(v, long(4));
2550    }
2551
2552    #[test]
2553    fn test_post_condition_no_pre() {
2554        // :post only (no :pre).
2555        let v = eval_str("(defn h [x] {:post [(number? %)]} (inc x)) (h 2)").unwrap();
2556        assert_eq!(v, long(3));
2557    }
2558
2559    #[test]
2560    fn test_pre_condition_no_post() {
2561        // :pre only (no :post); existing test variant without conditions map.
2562        let v = eval_str("(defn h [x] {:pre [(number? x)]} x) (h 42)").unwrap();
2563        assert_eq!(v, long(42));
2564    }
2565}