Skip to main content

cljrs_value/
types.rs

1//! Stub types for Phase 4/7 that are referenced by the Value enum.
2
3#![allow(unused)]
4
5use std::collections::HashMap;
6use std::mem;
7use std::sync::{Arc, Condvar, Mutex};
8
9use cljrs_gc::GcPtr;
10use cljrs_reader::Form;
11
12use crate::Value;
13
14// ── No-GC debug provenance helper ────────────────────────────────────────────
15
16/// In `no-gc` debug builds: return `true` if the top-level `GcPtr` inside
17/// `value` (if any) was allocated by the global `StaticArena`.
18///
19/// Primitives (`Nil`, `Bool`, `Long`, `Double`, `Char`) contain no `GcPtr`
20/// and always return `true`.  `Resource` is Arc-managed and also returns
21/// `true`.  All other variants have a `GcPtr` that is checked against the
22/// static arena's chunk range.
23///
24/// This check is intentionally **shallow** (top-level pointer only).  If the
25/// value was produced inside a `StaticCtxGuard`, ALL allocations during its
26/// evaluation go to the static arena — so a static top-level pointer implies
27/// static contents.
28#[cfg(all(feature = "no-gc", debug_assertions))]
29pub(crate) fn value_gcptr_is_static(value: &Value) -> bool {
30    use crate::value::MapValue;
31    use crate::value::SetValue;
32    match value {
33        // Inline scalars — no GcPtr.
34        Value::Nil
35        | Value::Bool(_)
36        | Value::Long(_)
37        | Value::Double(_)
38        | Value::Char(_)
39        | Value::Uuid(_) => true,
40        // Arc-managed — not GcPtr.
41        Value::Resource(_) => true,
42        // GcPtr variants.
43        Value::BigInt(p) => p.is_static_alloc(),
44        Value::BigDecimal(p) => p.is_static_alloc(),
45        Value::Ratio(p) => p.is_static_alloc(),
46        Value::Str(p) => p.is_static_alloc(),
47        Value::Pattern(p) => p.is_static_alloc(),
48        Value::Matcher(p) => p.is_static_alloc(),
49        Value::Symbol(p) => p.is_static_alloc(),
50        Value::Keyword(p) => p.is_static_alloc(),
51        Value::List(p) => p.is_static_alloc(),
52        Value::Vector(p) => p.is_static_alloc(),
53        Value::Queue(p) => p.is_static_alloc(),
54        Value::Map(m) => match m {
55            MapValue::Array(p) => p.is_static_alloc(),
56            MapValue::Hash(p) => p.is_static_alloc(),
57            MapValue::Sorted(p) => p.is_static_alloc(),
58        },
59        Value::Set(s) => match s {
60            SetValue::Hash(p) => p.is_static_alloc(),
61            SetValue::Sorted(p) => p.is_static_alloc(),
62        },
63        Value::NativeFunction(p) => p.is_static_alloc(),
64        Value::Fn(p) | Value::Macro(p) => p.is_static_alloc(),
65        Value::BoundFn(p) => p.is_static_alloc(),
66        Value::Var(p) => p.is_static_alloc(),
67        Value::Atom(p) => p.is_static_alloc(),
68        Value::Namespace(p) => p.is_static_alloc(),
69        Value::LazySeq(p) => p.is_static_alloc(),
70        Value::Cons(p) => p.is_static_alloc(),
71        Value::Protocol(p) => p.is_static_alloc(),
72        Value::ProtocolFn(p) => p.is_static_alloc(),
73        Value::MultiFn(p) => p.is_static_alloc(),
74        Value::Volatile(p) => p.is_static_alloc(),
75        Value::Delay(p) => p.is_static_alloc(),
76        Value::Promise(p) => p.is_static_alloc(),
77        Value::Future(p) => p.is_static_alloc(),
78        Value::Agent(p) => p.is_static_alloc(),
79        Value::TypeInstance(p) => p.is_static_alloc(),
80        Value::ObjectArray(p) => p.is_static_alloc(),
81        Value::NativeObject(p) => p.is_static_alloc(),
82        Value::Error(p) => p.is_static_alloc(),
83        Value::TransientMap(p) => p.is_static_alloc(),
84        Value::TransientVector(p) => p.is_static_alloc(),
85        Value::TransientSet(p) => p.is_static_alloc(),
86        // Primitive arrays — no meaningful pointer check needed.
87        Value::BooleanArray(_)
88        | Value::ByteArray(_)
89        | Value::ShortArray(_)
90        | Value::IntArray(_)
91        | Value::LongArray(_)
92        | Value::FloatArray(_)
93        | Value::DoubleArray(_)
94        | Value::CharArray(_) => true,
95        // Wrapper variants.
96        Value::Reduced(inner) | Value::WithMeta(inner, _) => value_gcptr_is_static(inner),
97    }
98}
99
100// ── Protocol ──────────────────────────────────────────────────────────────────
101
102/// Inner map type for protocol implementations: method_name → impl fn.
103pub type MethodMap = HashMap<Arc<str>, Value>;
104
105/// A Clojure protocol — an interface-like construct with named methods.
106#[derive(Debug)]
107pub struct Protocol {
108    pub name: Arc<str>,
109    pub ns: Arc<str>,
110    pub methods: Vec<ProtocolMethod>,
111    /// type_tag → { method_name → impl fn }
112    pub impls: Mutex<HashMap<Arc<str>, MethodMap>>,
113}
114
115impl Protocol {
116    pub fn new(name: Arc<str>, ns: Arc<str>, methods: Vec<ProtocolMethod>) -> Self {
117        Self {
118            name,
119            ns,
120            methods,
121            impls: Mutex::new(HashMap::new()),
122        }
123    }
124}
125
126impl cljrs_gc::Trace for Protocol {
127    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
128        {
129            let impls = self.impls.lock().unwrap();
130            for method_map in impls.values() {
131                for v in method_map.values() {
132                    v.trace(visitor);
133                }
134            }
135        }
136    }
137}
138
139/// One method signature declared in a `defprotocol`.
140#[derive(Debug, Clone)]
141pub struct ProtocolMethod {
142    pub name: Arc<str>,
143    pub min_arity: usize,
144    pub variadic: bool,
145}
146
147impl cljrs_gc::Trace for ProtocolMethod {
148    fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
149}
150
151// ── ProtocolFn ────────────────────────────────────────────────────────────────
152
153/// Callable that dispatches a single protocol method on the type of `args[0]`.
154#[derive(Debug)]
155pub struct ProtocolFn {
156    pub protocol: GcPtr<Protocol>,
157    pub method_name: Arc<str>,
158    pub min_arity: usize,
159    pub variadic: bool,
160}
161
162impl cljrs_gc::Trace for ProtocolFn {
163    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
164        use cljrs_gc::GcVisitor as _;
165        visitor.visit(&self.protocol);
166    }
167}
168
169// ── MultiFn ───────────────────────────────────────────────────────────────────
170
171/// A Clojure multimethod — arbitrary dispatch via a user-supplied function.
172#[derive(Debug)]
173pub struct MultiFn {
174    pub name: Arc<str>,
175    pub dispatch_fn: Value,
176    /// pr_str(dispatch-val) → implementation fn
177    pub methods: Mutex<HashMap<String, Value>>,
178    /// recorded preferences (for future derive/hierarchy)
179    pub prefers: Mutex<HashMap<String, Vec<String>>>,
180    /// normally ":default"
181    pub default_dispatch: String,
182}
183
184impl MultiFn {
185    pub fn new(name: Arc<str>, dispatch_fn: Value, default_dispatch: String) -> Self {
186        Self {
187            name,
188            dispatch_fn,
189            methods: Mutex::new(HashMap::new()),
190            prefers: Mutex::new(HashMap::new()),
191            default_dispatch,
192        }
193    }
194}
195
196impl cljrs_gc::Trace for MultiFn {
197    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
198        self.dispatch_fn.trace(visitor);
199        {
200            let methods = self.methods.lock().unwrap();
201            for v in methods.values() {
202                v.trace(visitor);
203            }
204        }
205    }
206}
207
208// ── Var ───────────────────────────────────────────────────────────────────────
209
210/// A Clojure var — a namespace-interned mutable root binding.
211#[derive(Debug)]
212pub struct Var {
213    pub namespace: Arc<str>,
214    pub name: Arc<str>,
215    pub value: Mutex<Option<Value>>,
216    pub is_macro: bool,
217    /// Metadata map (e.g. `{:dynamic true}`).
218    pub meta: Mutex<Option<Value>>,
219    pub watches: Mutex<Vec<(Value, Value)>>,
220}
221
222impl Var {
223    pub fn new(namespace: impl Into<Arc<str>>, name: impl Into<Arc<str>>) -> Self {
224        Self {
225            namespace: namespace.into(),
226            name: name.into(),
227            value: Mutex::new(None),
228            is_macro: false,
229            meta: Mutex::new(None),
230            watches: Mutex::new(Vec::new()),
231        }
232    }
233
234    pub fn is_bound(&self) -> bool {
235        self.value.lock().unwrap().is_some()
236    }
237
238    pub fn deref(&self) -> Option<Value> {
239        self.value.lock().unwrap().clone()
240    }
241
242    pub fn bind(&self, v: Value) {
243        // In no-gc debug builds: assert the value being stored in this
244        // program-lifetime Var came from the StaticArena, not a scratch region.
245        // A region-local pointer would dangle after the function returns.
246        #[cfg(all(feature = "no-gc", debug_assertions))]
247        debug_assert!(
248            value_gcptr_is_static(&v),
249            "no-gc: Var::bind({}/{}) received a region-local value — store violations \
250             indicate a missing StaticCtxGuard around the value expression",
251            self.namespace,
252            self.name
253        );
254        *self.value.lock().unwrap() = Some(v);
255    }
256
257    pub fn get_meta(&self) -> Option<Value> {
258        self.meta.lock().unwrap().clone()
259    }
260
261    pub fn set_meta(&self, m: Value) {
262        *self.meta.lock().unwrap() = Some(m);
263    }
264
265    pub fn full_name(&self) -> String {
266        format!("{}/{}", self.namespace, self.name)
267    }
268}
269
270impl cljrs_gc::Trace for Var {
271    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
272        {
273            let value = self.value.lock().unwrap();
274            if let Some(v) = value.as_ref() {
275                v.trace(visitor);
276            }
277        }
278        {
279            let meta = self.meta.lock().unwrap();
280            if let Some(m) = meta.as_ref() {
281                m.trace(visitor);
282            }
283        }
284        {
285            let watches = self.watches.lock().unwrap();
286            for (key, f) in watches.iter() {
287                key.trace(visitor);
288                f.trace(visitor);
289            }
290        }
291    }
292}
293
294// ── Atom ──────────────────────────────────────────────────────────────────────
295
296/// A Clojure atom — a thread-safe mutable reference.
297#[derive(Debug)]
298pub struct Atom {
299    pub value: Mutex<Value>,
300    pub meta: Mutex<Option<Value>>,
301    pub validator: Mutex<Option<Value>>,
302    pub watches: Mutex<Vec<(Value, Value)>>,
303}
304
305impl Atom {
306    pub fn new(v: Value) -> Self {
307        Self {
308            value: Mutex::new(v),
309            meta: Mutex::new(None),
310            validator: Mutex::new(None),
311            watches: Mutex::new(Vec::new()),
312        }
313    }
314
315    pub fn deref(&self) -> Value {
316        self.value.lock().unwrap().clone()
317    }
318
319    pub fn reset(&self, v: Value) -> Value {
320        // In no-gc debug builds: assert the new value came from the StaticArena.
321        #[cfg(all(feature = "no-gc", debug_assertions))]
322        debug_assert!(
323            value_gcptr_is_static(&v),
324            "no-gc: Atom::reset() received a region-local value — the new-value \
325             expression must be computed inside a StaticCtxGuard (i.e. inside \
326             the swap! / reset! call) so it is allocated in the static arena"
327        );
328        let mut guard = self.value.lock().unwrap();
329        *guard = v.clone();
330        v
331    }
332
333    pub fn get_meta(&self) -> Option<Value> {
334        self.meta.lock().unwrap().clone()
335    }
336
337    pub fn set_meta(&self, m: Option<Value>) {
338        *self.meta.lock().unwrap() = m;
339    }
340
341    pub fn get_validator(&self) -> Option<Value> {
342        self.validator.lock().unwrap().clone()
343    }
344
345    pub fn set_validator(&self, vf: Option<Value>) {
346        *self.validator.lock().unwrap() = vf;
347    }
348}
349
350impl cljrs_gc::Trace for Atom {
351    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
352        {
353            let value = self.value.lock().unwrap();
354            value.trace(visitor);
355        }
356        {
357            let meta = self.meta.lock().unwrap();
358            if let Some(m) = meta.as_ref() {
359                m.trace(visitor);
360            }
361        }
362        {
363            let validator = self.validator.lock().unwrap();
364            if let Some(vf) = validator.as_ref() {
365                vf.trace(visitor);
366            }
367        }
368        {
369            let watches = self.watches.lock().unwrap();
370            for (key, f) in watches.iter() {
371                key.trace(visitor);
372                f.trace(visitor);
373            }
374        }
375    }
376}
377
378// ── Namespace ─────────────────────────────────────────────────────────────────
379
380/// A Clojure namespace with intern table, refers, and aliases.
381#[derive(Debug)]
382pub struct Namespace {
383    pub name: Arc<str>,
384    /// Vars interned directly in this namespace.
385    pub interns: Mutex<HashMap<Arc<str>, GcPtr<Var>>>,
386    /// Vars referred from other namespaces (e.g. clojure.core).
387    pub refers: Mutex<HashMap<Arc<str>, GcPtr<Var>>>,
388    /// Namespace aliases: short-name → full namespace name.
389    pub aliases: Mutex<HashMap<Arc<str>, Arc<str>>>,
390    /// Absolute path of the source file this namespace was loaded from,
391    /// populated by the loader.  Used by the versioned resolver to locate the
392    /// file for `git show`.
393    pub source_file: Mutex<Option<Arc<str>>>,
394    /// Absolute path of the git repository root that contains `source_file`.
395    pub git_repo_root: Mutex<Option<Arc<str>>>,
396    /// `true` for namespaces loaded from a specific commit (`name@hash`).
397    /// Versioned namespaces are immutable: `intern()` will refuse new bindings.
398    pub is_versioned: bool,
399}
400
401impl Namespace {
402    pub fn new(name: impl Into<Arc<str>>) -> Self {
403        Self {
404            name: name.into(),
405            interns: Mutex::new(HashMap::new()),
406            refers: Mutex::new(HashMap::new()),
407            aliases: Mutex::new(HashMap::new()),
408            source_file: Mutex::new(None),
409            git_repo_root: Mutex::new(None),
410            is_versioned: false,
411        }
412    }
413
414    /// Create a versioned (immutable) namespace for `name@commit`.
415    pub fn new_versioned(name: impl Into<Arc<str>>) -> Self {
416        Self {
417            is_versioned: true,
418            ..Self::new(name)
419        }
420    }
421
422    /// Record the source file path and its git repo root (if in a repo).
423    pub fn set_source_location(&self, file: &str, repo_root: Option<&str>) {
424        *self.source_file.lock().unwrap() = Some(Arc::from(file));
425        *self.git_repo_root.lock().unwrap() = repo_root.map(Arc::from);
426    }
427}
428
429impl cljrs_gc::Trace for Namespace {
430    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
431        use cljrs_gc::GcVisitor as _;
432        {
433            let interns = self.interns.lock().unwrap();
434            for var in interns.values() {
435                visitor.visit(var);
436            }
437        }
438        {
439            let refers = self.refers.lock().unwrap();
440            for var in refers.values() {
441                visitor.visit(var);
442            }
443        }
444    }
445}
446
447// ── NativeFn ──────────────────────────────────────────────────────────────────
448
449/// A Rust function callable from Clojure.
450/// Legacy type alias kept for source compatibility. Bare `fn` pointers
451/// implement `Fn` and can be passed anywhere a `NativeFnFunc` is expected.
452pub type NativeFnPtr = fn(&[Value]) -> crate::error::ValueResult<Value>;
453
454/// The callable stored inside a `NativeFn`. Supports both bare function
455/// pointers and closures that capture state.
456pub type NativeFnFunc = Arc<dyn Fn(&[Value]) -> crate::error::ValueResult<Value> + Send + Sync>;
457
458#[derive(Clone, Debug)]
459pub enum Arity {
460    Fixed(usize),
461    Variadic { min: usize },
462}
463
464pub struct NativeFn {
465    pub name: Arc<str>,
466    pub arity: Arity,
467    pub func: NativeFnFunc,
468}
469
470impl NativeFn {
471    /// Create from a bare function pointer (backwards-compatible).
472    pub fn new(name: impl Into<Arc<str>>, arity: Arity, func: NativeFnPtr) -> Self {
473        Self {
474            name: name.into(),
475            arity,
476            func: Arc::new(func),
477        }
478    }
479
480    /// Create from a closure or any `Fn(&[Value]) -> ValueResult<Value>`.
481    pub fn with_closure(
482        name: impl Into<Arc<str>>,
483        arity: Arity,
484        func: impl Fn(&[Value]) -> crate::error::ValueResult<Value> + Send + Sync + 'static,
485    ) -> Self {
486        Self {
487            name: name.into(),
488            arity,
489            func: Arc::new(func),
490        }
491    }
492}
493
494impl std::fmt::Debug for NativeFn {
495    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496        f.debug_struct("NativeFn")
497            .field("name", &self.name)
498            .field("arity", &self.arity)
499            .field("func", &"<fn>")
500            .finish()
501    }
502}
503
504impl cljrs_gc::Trace for NativeFn {
505    fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
506}
507
508// ── CljxFnArity ───────────────────────────────────────────────────────────────
509
510/// One arity branch of a Clojure function.
511#[derive(Debug, Clone)]
512pub struct CljxFnArity {
513    /// Simple parameter names (no `&`).
514    /// For destructured params, these are gensym'd names.
515    pub params: Vec<Arc<str>>,
516    /// The name after `&`, if any.
517    pub rest_param: Option<Arc<str>>,
518    /// The body forms for this arity.
519    pub body: Vec<Form>,
520    /// Destructuring patterns: (param_index, original_form).
521    /// After binding the gensym'd param, these patterns are applied
522    /// via `bind_pattern` to destructure the value.
523    pub destructure_params: Vec<(usize, Form)>,
524    /// If the rest param is destructured, the original form.
525    pub destructure_rest: Option<Form>,
526    /// Unique ID for IR cache lookup (assigned by the evaluator).
527    pub ir_arity_id: u64,
528}
529
530impl CljxFnArity {
531    /// Heap bytes owned by this arity, not counting the `CljxFnArity` struct itself.
532    pub fn heap_size(&self) -> usize {
533        // params Vec buffer (Arc<str> pointers; the str data is shared, skip it)
534        self.params.capacity() * mem::size_of::<Arc<str>>()
535        // body: the dominant consumer — Form AST trees stored inline
536        + self.body.capacity() * mem::size_of::<Form>()
537        + self.body.iter().map(|f| f.heap_size()).sum::<usize>()
538        // destructure_params
539        + self.destructure_params.capacity() * mem::size_of::<(usize, Form)>()
540        + self.destructure_params.iter().map(|(_, f)| f.heap_size()).sum::<usize>()
541        // destructure_rest
542        + self.destructure_rest.as_ref()
543            .map_or(0, |f| mem::size_of::<Form>() + f.heap_size())
544    }
545}
546
547// ── CljxFn ────────────────────────────────────────────────────────────────────
548
549/// An interpreted Clojure closure with captured environment.
550#[derive(Debug, Clone)]
551pub struct CljxFn {
552    pub name: Option<Arc<str>>,
553    pub arities: Vec<CljxFnArity>,
554    /// Names of closed-over bindings (parallel to `closed_over_vals`).
555    pub closed_over_names: Vec<Arc<str>>,
556    /// Values of closed-over bindings (parallel to `closed_over_names`).
557    pub closed_over_vals: Vec<Value>,
558    /// True if this function was defined with `defmacro`.
559    pub is_macro: bool,
560    /// True if this function carries `^:async` metadata. When an async runtime
561    /// (`cljrs-async`) is registered, calling such a function spawns its body as
562    /// a task and returns a `Value::Future` immediately instead of running it
563    /// synchronously. Without a runtime it runs synchronously like any other fn.
564    pub is_async: bool,
565    /// Namespace in which this function was defined (for macro hygiene).
566    pub defining_ns: Arc<str>,
567}
568
569impl CljxFn {
570    pub fn new(
571        name: Option<Arc<str>>,
572        arities: Vec<CljxFnArity>,
573        closed_over_names: Vec<Arc<str>>,
574        closed_over_vals: Vec<Value>,
575        is_macro: bool,
576        defining_ns: Arc<str>,
577    ) -> Self {
578        Self {
579            name,
580            arities,
581            closed_over_names,
582            closed_over_vals,
583            is_macro,
584            is_async: false,
585            defining_ns,
586        }
587    }
588}
589
590impl cljrs_gc::Trace for CljxFn {
591    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
592        for v in &self.closed_over_vals {
593            v.trace(visitor);
594        }
595    }
596
597    fn gc_size_extra(&self) -> usize {
598        // Vec<CljxFnArity> buffer + each arity's inline-owned heap
599        self.arities.capacity() * mem::size_of::<CljxFnArity>()
600            + self
601                .arities
602                .iter()
603                .map(CljxFnArity::heap_size)
604                .sum::<usize>()
605    }
606}
607
608// ── BoundFn ──────────────────────────────────────────────────────────────────
609
610/// A function wrapped with captured dynamic bindings.
611/// When called, the captured bindings are pushed as a frame before delegating
612/// to the wrapped function. This means captured bindings override the caller's
613/// for the same var, but vars not in the capture fall through normally.
614#[derive(Debug)]
615pub struct BoundFn {
616    /// The wrapped callable.
617    pub wrapped: Value,
618    /// Captured dynamic bindings (merged flat frame; opaque to cljrs-value).
619    pub captured_bindings: HashMap<usize, Value>,
620}
621
622impl cljrs_gc::Trace for BoundFn {
623    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
624        self.wrapped.trace(visitor);
625        for val in self.captured_bindings.values() {
626            val.trace(visitor);
627        }
628    }
629
630    fn gc_size_extra(&self) -> usize {
631        // HashMap<usize, Value>: hashbrown open-addressing, ~1 control byte + entry per slot.
632        self.captured_bindings.capacity() * (1 + mem::size_of::<usize>() + mem::size_of::<Value>())
633    }
634}
635
636// ── Thunk / LazySeq ───────────────────────────────────────────────────────────
637
638/// A deferred computation that produces a `Value` when forced.
639pub trait Thunk: Send + Sync + std::fmt::Debug + cljrs_gc::Trace {
640    fn force(&self) -> Result<Value, String>;
641}
642
643/// Internal state of a lazy sequence cell.
644pub enum LazySeqState {
645    /// Thunk not yet evaluated.
646    Pending(Box<dyn Thunk>),
647    /// Result cached after first force.
648    Forced(Value),
649    /// Thunk evaluation failed; error message is cached.
650    Error(String),
651}
652
653/// A lazy sequence that forces its thunk exactly once and caches the result.
654pub struct LazySeq {
655    pub state: Mutex<LazySeqState>,
656}
657
658impl LazySeq {
659    pub fn new(thunk: Box<dyn Thunk>) -> Self {
660        Self {
661            state: Mutex::new(LazySeqState::Pending(thunk)),
662        }
663    }
664
665    /// Realize the sequence: force the thunk on first call, return cached value on subsequent calls.
666    /// On error, returns `Value::Nil` and caches the error (retrievable via `error()`).
667    pub fn realize(&self) -> Value {
668        let thunk = {
669            let mut guard = self.state.lock().unwrap();
670            match &*guard {
671                LazySeqState::Forced(v) => return v.clone(),
672                LazySeqState::Error(_) => return Value::Nil,
673                LazySeqState::Pending(_) => {}
674            }
675            // Replace the pending state with a temporary Forced(Nil), extract the thunk.
676            let prev = mem::replace(&mut *guard, LazySeqState::Forced(Value::Nil));
677            let LazySeqState::Pending(thunk) = prev else {
678                unreachable!("state was not Pending")
679            };
680            thunk
681            // guard dropped here — lock released before forcing
682        };
683        // Force the thunk WITHOUT holding the lock. This ensures GC's
684        // lock().unwrap() in LazySeq::trace() will not deadlock.
685        match thunk.force() {
686            Ok(result) => {
687                *self.state.lock().unwrap() = LazySeqState::Forced(result.clone());
688                result
689            }
690            Err(msg) => {
691                *self.state.lock().unwrap() = LazySeqState::Error(msg);
692                Value::Nil
693            }
694        }
695    }
696
697    /// Return the cached error message, if the thunk failed.
698    pub fn error(&self) -> Option<String> {
699        let guard = self.state.lock().unwrap();
700        if let LazySeqState::Error(e) = &*guard {
701            Some(e.clone())
702        } else {
703            None
704        }
705    }
706}
707
708impl std::fmt::Debug for LazySeq {
709    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
710        write!(f, "LazySeq(...)")
711    }
712}
713
714impl cljrs_gc::Trace for LazySeq {
715    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
716        // Safe to lock unconditionally: realize() drops the lock before entering
717        // eval (thunk.force()), so the lock is never held across a GC safepoint.
718        {
719            let state = self.state.lock().unwrap();
720            match &*state {
721                LazySeqState::Pending(thunk) => thunk.trace(visitor),
722                LazySeqState::Forced(v) => v.trace(visitor),
723                LazySeqState::Error(_) => {}
724            }
725        }
726    }
727}
728
729// ── CljxCons ──────────────────────────────────────────────────────────────────
730
731/// A lazy cons cell: head element + tail (may be a `LazySeq`, `List`, or `Nil`).
732///
733/// Used when `cons` is called with a `LazySeq` or `Cons` tail, enabling lazy
734/// sequences without eagerly realizing them.
735#[derive(Debug, Clone)]
736pub struct CljxCons {
737    pub head: Value,
738    pub tail: Value,
739}
740
741impl cljrs_gc::Trace for CljxCons {
742    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
743        self.head.trace(visitor);
744        self.tail.trace(visitor);
745    }
746}
747
748// ── Volatile ──────────────────────────────────────────────────────────────────
749
750/// Non-atomic mutable cell (single-thread performance, no CAS).
751pub struct Volatile {
752    pub value: Mutex<Value>,
753}
754
755impl Volatile {
756    pub fn new(v: Value) -> Self {
757        Self {
758            value: Mutex::new(v),
759        }
760    }
761
762    pub fn deref(&self) -> Value {
763        self.value.lock().unwrap().clone()
764    }
765
766    pub fn reset(&self, v: Value) -> Value {
767        // In no-gc debug builds: assert the new value came from the StaticArena.
768        #[cfg(all(feature = "no-gc", debug_assertions))]
769        debug_assert!(
770            value_gcptr_is_static(&v),
771            "no-gc: Volatile::reset() received a region-local value — ensure the \
772             new-value expression is inside a StaticCtxGuard (vreset! handles this)"
773        );
774        *self.value.lock().unwrap() = v.clone();
775        v
776    }
777}
778
779impl std::fmt::Debug for Volatile {
780    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
781        write!(f, "Volatile")
782    }
783}
784
785impl cljrs_gc::Trace for Volatile {
786    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
787        {
788            let value = self.value.lock().unwrap();
789            value.trace(visitor);
790        }
791    }
792}
793
794// ── Delay ─────────────────────────────────────────────────────────────────────
795
796/// Internal state of a delay cell.
797pub enum DelayState {
798    Pending(Box<dyn Thunk>),
799    Forced(Value),
800}
801
802/// A lazy one-time computation (forced at most once, result cached).
803pub struct Delay {
804    pub state: Mutex<DelayState>,
805}
806
807impl Delay {
808    pub fn new(thunk: Box<dyn Thunk>) -> Self {
809        Self {
810            state: Mutex::new(DelayState::Pending(thunk)),
811        }
812    }
813
814    /// Force the delay and cache the result.
815    /// Returns the value on success, or an error message on failure.
816    pub fn force(&self) -> Result<Value, String> {
817        let thunk = {
818            let mut guard = self.state.lock().unwrap();
819            if let DelayState::Forced(v) = &*guard {
820                return Ok(v.clone());
821            }
822            let prev = mem::replace(&mut *guard, DelayState::Forced(Value::Nil));
823            let DelayState::Pending(thunk) = prev else {
824                unreachable!("state was not Pending")
825            };
826            thunk
827            // guard dropped here — lock released before forcing
828        };
829        // Force the thunk WITHOUT holding the lock so GC's lock().unwrap() in
830        // Delay::trace() will not deadlock.
831        let result = thunk.force()?;
832        *self.state.lock().unwrap() = DelayState::Forced(result.clone());
833        Ok(result)
834    }
835
836    /// True if the delay has already been forced.
837    pub fn is_realized(&self) -> bool {
838        matches!(&*self.state.lock().unwrap(), DelayState::Forced(_))
839    }
840}
841
842impl std::fmt::Debug for Delay {
843    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
844        write!(f, "Delay")
845    }
846}
847
848impl cljrs_gc::Trace for Delay {
849    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
850        // Safe to lock unconditionally: force() drops the lock before entering
851        // eval (thunk.force()), so the lock is never held across a GC safepoint.
852        {
853            let state = self.state.lock().unwrap();
854            match &*state {
855                DelayState::Pending(thunk) => thunk.trace(visitor),
856                DelayState::Forced(v) => v.trace(visitor),
857            }
858        }
859    }
860}
861
862// ── CljxPromise ───────────────────────────────────────────────────────────────
863
864/// A one-shot rendezvous (promise).
865pub struct CljxPromise {
866    pub value: Mutex<Option<Value>>,
867    pub cond: Condvar,
868}
869
870impl CljxPromise {
871    pub fn new() -> Self {
872        Self {
873            value: Mutex::new(None),
874            cond: Condvar::new(),
875        }
876    }
877
878    /// Deliver a value (no-op if already delivered).
879    pub fn deliver(&self, v: Value) {
880        let mut guard = self.value.lock().unwrap();
881        if guard.is_none() {
882            *guard = Some(v);
883            self.cond.notify_all();
884        }
885    }
886
887    /// Block until a value is available, then return it.
888    pub fn deref_blocking(&self) -> Value {
889        let mut guard = self.value.lock().unwrap();
890        while guard.is_none() {
891            guard = self.cond.wait(guard).unwrap();
892        }
893        guard.as_ref().unwrap().clone()
894    }
895
896    /// True if already delivered.
897    pub fn is_realized(&self) -> bool {
898        self.value.lock().unwrap().is_some()
899    }
900}
901
902impl Default for CljxPromise {
903    fn default() -> Self {
904        Self::new()
905    }
906}
907
908impl std::fmt::Debug for CljxPromise {
909    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
910        write!(f, "Promise")
911    }
912}
913
914impl cljrs_gc::Trace for CljxPromise {
915    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
916        {
917            let value = self.value.lock().unwrap();
918            if let Some(v) = value.as_ref() {
919                v.trace(visitor);
920            }
921        }
922    }
923}
924
925// ── CljxFuture ────────────────────────────────────────────────────────────────
926
927/// Thread-pool future state.
928pub enum FutureState {
929    Running,
930    Done(Value),
931    Failed(String),
932    Cancelled,
933}
934
935/// A future value computed asynchronously on another thread.
936pub struct CljxFuture {
937    pub state: Mutex<FutureState>,
938    pub cond: Condvar,
939}
940
941impl CljxFuture {
942    pub fn new() -> Self {
943        Self {
944            state: Mutex::new(FutureState::Running),
945            cond: Condvar::new(),
946        }
947    }
948
949    /// True if done, failed, or cancelled (not still running).
950    pub fn is_done(&self) -> bool {
951        !matches!(&*self.state.lock().unwrap(), FutureState::Running)
952    }
953
954    /// True if explicitly cancelled.
955    pub fn is_cancelled(&self) -> bool {
956        matches!(&*self.state.lock().unwrap(), FutureState::Cancelled)
957    }
958}
959
960impl Default for CljxFuture {
961    fn default() -> Self {
962        Self::new()
963    }
964}
965
966impl std::fmt::Debug for CljxFuture {
967    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
968        write!(f, "Future")
969    }
970}
971
972impl cljrs_gc::Trace for CljxFuture {
973    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
974        {
975            let state = self.state.lock().unwrap();
976            if let FutureState::Done(v) = &*state {
977                v.trace(visitor);
978            }
979        }
980    }
981}
982
983// ── Agent ─────────────────────────────────────────────────────────────────────
984
985/// A Clojure agent action: takes the current state, returns the new state.
986pub type AgentFn = Box<dyn FnOnce(Value) -> Result<Value, Value> + Send>;
987
988/// Messages sent to an agent's worker thread.
989pub enum AgentMsg {
990    Update(AgentFn),
991    Shutdown,
992}
993
994/// A Clojure agent — asynchronous state update queue.
995pub struct Agent {
996    /// Current state, shared between the Value::Agent handle and the worker thread.
997    pub state: Arc<Mutex<Value>>,
998    /// Last error, shared similarly.
999    pub error: Arc<Mutex<Option<Value>>>,
1000    /// Channel to send actions to the worker thread.
1001    pub sender: Mutex<std::sync::mpsc::SyncSender<AgentMsg>>,
1002    pub watches: Mutex<Vec<(Value, Value)>>,
1003}
1004
1005impl Agent {
1006    pub fn get_state(&self) -> Value {
1007        self.state.lock().unwrap().clone()
1008    }
1009
1010    pub fn get_error(&self) -> Option<Value> {
1011        self.error.lock().unwrap().clone()
1012    }
1013
1014    pub fn clear_error(&self) {
1015        *self.error.lock().unwrap() = None;
1016    }
1017}
1018
1019impl std::fmt::Debug for Agent {
1020    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1021        write!(f, "Agent")
1022    }
1023}
1024
1025impl cljrs_gc::Trace for Agent {
1026    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1027        {
1028            let state = self.state.lock().unwrap();
1029            state.trace(visitor);
1030        }
1031        {
1032            let error = self.error.lock().unwrap();
1033            if let Some(e) = error.as_ref() {
1034                e.trace(visitor);
1035            }
1036        }
1037        {
1038            let watches = self.watches.lock().unwrap();
1039            for (key, f) in watches.iter() {
1040                key.trace(visitor);
1041                f.trace(visitor);
1042            }
1043        }
1044    }
1045}