Skip to main content

cljrs_runtime/interp/
eval.rs

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