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