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; always considered static.
41        Value::Resource(_) | Value::SharedAtom(_) | Value::ByteBlob(_) => 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        // Replace the binding, holding the lock only across the swap.  The
255        // previous value (if any) is handed to the JIT rebind hook so it can
256        // reclaim native code compiled for a now-superseded definition
257        // (Phase 10.2 — code unloading).  `v.clone()` is O(1) for the only
258        // values that carry compiled code (`Value::Fn`, a `GcPtr` clone).
259        let prev = {
260            let mut slot = self.value.lock().unwrap();
261            slot.replace(v.clone())
262        };
263        if let Some(prev) = prev {
264            crate::jit_hooks::notify_var_rebind(&prev, &v);
265        }
266    }
267
268    pub fn get_meta(&self) -> Option<Value> {
269        self.meta.lock().unwrap().clone()
270    }
271
272    pub fn set_meta(&self, m: Value) {
273        *self.meta.lock().unwrap() = Some(m);
274    }
275
276    pub fn full_name(&self) -> String {
277        format!("{}/{}", self.namespace, self.name)
278    }
279}
280
281impl cljrs_gc::Trace for Var {
282    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
283        {
284            let value = self.value.lock().unwrap();
285            if let Some(v) = value.as_ref() {
286                v.trace(visitor);
287            }
288        }
289        {
290            let meta = self.meta.lock().unwrap();
291            if let Some(m) = meta.as_ref() {
292                m.trace(visitor);
293            }
294        }
295        {
296            let watches = self.watches.lock().unwrap();
297            for (key, f) in watches.iter() {
298                key.trace(visitor);
299                f.trace(visitor);
300            }
301        }
302    }
303}
304
305// ── Atom ──────────────────────────────────────────────────────────────────────
306
307/// A Clojure atom — a thread-safe mutable reference.
308#[derive(Debug)]
309pub struct Atom {
310    pub value: Mutex<Value>,
311    pub meta: Mutex<Option<Value>>,
312    pub validator: Mutex<Option<Value>>,
313    pub watches: Mutex<Vec<(Value, Value)>>,
314}
315
316impl Atom {
317    pub fn new(v: Value) -> Self {
318        Self {
319            value: Mutex::new(v),
320            meta: Mutex::new(None),
321            validator: Mutex::new(None),
322            watches: Mutex::new(Vec::new()),
323        }
324    }
325
326    pub fn deref(&self) -> Value {
327        self.value.lock().unwrap().clone()
328    }
329
330    pub fn reset(&self, v: Value) -> Value {
331        // In no-gc debug builds: assert the new value came from the StaticArena.
332        #[cfg(all(feature = "no-gc", debug_assertions))]
333        debug_assert!(
334            value_gcptr_is_static(&v),
335            "no-gc: Atom::reset() received a region-local value — the new-value \
336             expression must be computed inside a StaticCtxGuard (i.e. inside \
337             the swap! / reset! call) so it is allocated in the static arena"
338        );
339        let mut guard = self.value.lock().unwrap();
340        *guard = v.clone();
341        v
342    }
343
344    pub fn get_meta(&self) -> Option<Value> {
345        self.meta.lock().unwrap().clone()
346    }
347
348    pub fn set_meta(&self, m: Option<Value>) {
349        *self.meta.lock().unwrap() = m;
350    }
351
352    pub fn get_validator(&self) -> Option<Value> {
353        self.validator.lock().unwrap().clone()
354    }
355
356    pub fn set_validator(&self, vf: Option<Value>) {
357        *self.validator.lock().unwrap() = vf;
358    }
359}
360
361impl cljrs_gc::Trace for Atom {
362    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
363        {
364            let value = self.value.lock().unwrap();
365            value.trace(visitor);
366        }
367        {
368            let meta = self.meta.lock().unwrap();
369            if let Some(m) = meta.as_ref() {
370                m.trace(visitor);
371            }
372        }
373        {
374            let validator = self.validator.lock().unwrap();
375            if let Some(vf) = validator.as_ref() {
376                vf.trace(visitor);
377            }
378        }
379        {
380            let watches = self.watches.lock().unwrap();
381            for (key, f) in watches.iter() {
382                key.trace(visitor);
383                f.trace(visitor);
384            }
385        }
386    }
387}
388
389// ── Namespace ─────────────────────────────────────────────────────────────────
390
391/// A Clojure namespace with intern table, refers, and aliases.
392#[derive(Debug)]
393pub struct Namespace {
394    pub name: Arc<str>,
395    /// Vars interned directly in this namespace.
396    pub interns: Mutex<HashMap<Arc<str>, GcPtr<Var>>>,
397    /// Vars referred from other namespaces (e.g. clojure.core).
398    pub refers: Mutex<HashMap<Arc<str>, GcPtr<Var>>>,
399    /// Namespace aliases: short-name → full namespace name.
400    pub aliases: Mutex<HashMap<Arc<str>, Arc<str>>>,
401    /// Absolute path of the source file this namespace was loaded from,
402    /// populated by the loader.  Used by the versioned resolver to locate the
403    /// file for `git show`.
404    pub source_file: Mutex<Option<Arc<str>>>,
405    /// Absolute path of the git repository root that contains `source_file`.
406    pub git_repo_root: Mutex<Option<Arc<str>>>,
407    /// `true` for namespaces loaded from a specific commit (`name@hash`).
408    /// Versioned namespaces are immutable: `intern()` will refuse new bindings.
409    pub is_versioned: bool,
410}
411
412impl Namespace {
413    pub fn new(name: impl Into<Arc<str>>) -> Self {
414        Self {
415            name: name.into(),
416            interns: Mutex::new(HashMap::new()),
417            refers: Mutex::new(HashMap::new()),
418            aliases: Mutex::new(HashMap::new()),
419            source_file: Mutex::new(None),
420            git_repo_root: Mutex::new(None),
421            is_versioned: false,
422        }
423    }
424
425    /// Create a versioned (immutable) namespace for `name@commit`.
426    pub fn new_versioned(name: impl Into<Arc<str>>) -> Self {
427        Self {
428            is_versioned: true,
429            ..Self::new(name)
430        }
431    }
432
433    /// Record the source file path and its git repo root (if in a repo).
434    pub fn set_source_location(&self, file: &str, repo_root: Option<&str>) {
435        *self.source_file.lock().unwrap() = Some(Arc::from(file));
436        *self.git_repo_root.lock().unwrap() = repo_root.map(Arc::from);
437    }
438}
439
440impl cljrs_gc::Trace for Namespace {
441    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
442        use cljrs_gc::GcVisitor as _;
443        {
444            let interns = self.interns.lock().unwrap();
445            for var in interns.values() {
446                visitor.visit(var);
447            }
448        }
449        {
450            let refers = self.refers.lock().unwrap();
451            for var in refers.values() {
452                visitor.visit(var);
453            }
454        }
455    }
456}
457
458// ── NativeFn ──────────────────────────────────────────────────────────────────
459
460/// A Rust function callable from Clojure.
461/// Legacy type alias kept for source compatibility. Bare `fn` pointers
462/// implement `Fn` and can be passed anywhere a `NativeFnFunc` is expected.
463pub type NativeFnPtr = fn(&[Value]) -> crate::error::ValueResult<Value>;
464
465/// The callable stored inside a `NativeFn`. Supports both bare function
466/// pointers and closures that capture state.
467pub type NativeFnFunc = Arc<dyn Fn(&[Value]) -> crate::error::ValueResult<Value>>;
468
469#[derive(Clone, Debug)]
470pub enum Arity {
471    Fixed(usize),
472    Variadic { min: usize },
473}
474
475pub struct NativeFn {
476    pub name: Arc<str>,
477    pub arity: Arity,
478    pub func: NativeFnFunc,
479}
480
481impl NativeFn {
482    /// Create from a bare function pointer (backwards-compatible).
483    pub fn new(name: impl Into<Arc<str>>, arity: Arity, func: NativeFnPtr) -> Self {
484        Self {
485            name: name.into(),
486            arity,
487            func: Arc::new(func),
488        }
489    }
490
491    /// Create from a closure or any `Fn(&[Value]) -> ValueResult<Value>`.
492    pub fn with_closure(
493        name: impl Into<Arc<str>>,
494        arity: Arity,
495        func: impl Fn(&[Value]) -> crate::error::ValueResult<Value> + 'static,
496    ) -> Self {
497        Self {
498            name: name.into(),
499            arity,
500            func: Arc::new(func),
501        }
502    }
503}
504
505impl std::fmt::Debug for NativeFn {
506    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
507        f.debug_struct("NativeFn")
508            .field("name", &self.name)
509            .field("arity", &self.arity)
510            .field("func", &"<fn>")
511            .finish()
512    }
513}
514
515impl cljrs_gc::Trace for NativeFn {
516    fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
517}
518
519// ── CljxFnArity ───────────────────────────────────────────────────────────────
520
521/// One arity branch of a Clojure function.
522#[derive(Debug, Clone)]
523pub struct CljxFnArity {
524    /// Simple parameter names (no `&`).
525    /// For destructured params, these are gensym'd names.
526    pub params: Vec<Arc<str>>,
527    /// The name after `&`, if any.
528    pub rest_param: Option<Arc<str>>,
529    /// The body forms for this arity.
530    pub body: Vec<Form>,
531    /// Destructuring patterns: (param_index, original_form).
532    /// After binding the gensym'd param, these patterns are applied
533    /// via `bind_pattern` to destructure the value.
534    pub destructure_params: Vec<(usize, Form)>,
535    /// If the rest param is destructured, the original form.
536    pub destructure_rest: Option<Form>,
537    /// Unique ID for IR cache lookup (assigned by the evaluator).
538    pub ir_arity_id: u64,
539}
540
541impl CljxFnArity {
542    /// Heap bytes owned by this arity, not counting the `CljxFnArity` struct itself.
543    pub fn heap_size(&self) -> usize {
544        // params Vec buffer (Arc<str> pointers; the str data is shared, skip it)
545        self.params.capacity() * mem::size_of::<Arc<str>>()
546        // body: the dominant consumer — Form AST trees stored inline
547        + self.body.capacity() * mem::size_of::<Form>()
548        + self.body.iter().map(|f| f.heap_size()).sum::<usize>()
549        // destructure_params
550        + self.destructure_params.capacity() * mem::size_of::<(usize, Form)>()
551        + self.destructure_params.iter().map(|(_, f)| f.heap_size()).sum::<usize>()
552        // destructure_rest
553        + self.destructure_rest.as_ref()
554            .map_or(0, |f| mem::size_of::<Form>() + f.heap_size())
555    }
556}
557
558// ── CljxFn ────────────────────────────────────────────────────────────────────
559
560/// An interpreted Clojure closure with captured environment.
561#[derive(Debug, Clone)]
562pub struct CljxFn {
563    pub name: Option<Arc<str>>,
564    pub arities: Vec<CljxFnArity>,
565    /// Names of closed-over bindings (parallel to `closed_over_vals`).
566    pub closed_over_names: Vec<Arc<str>>,
567    /// Values of closed-over bindings (parallel to `closed_over_names`).
568    pub closed_over_vals: Vec<Value>,
569    /// True if this function was defined with `defmacro`.
570    pub is_macro: bool,
571    /// True if this function carries `^:async` metadata. When an async runtime
572    /// (`cljrs-async`) is registered, calling such a function spawns its body as
573    /// a task and returns a `Value::Future` immediately instead of running it
574    /// synchronously. Without a runtime it runs synchronously like any other fn.
575    pub is_async: bool,
576    /// Namespace in which this function was defined (for macro hygiene).
577    pub defining_ns: Arc<str>,
578}
579
580impl CljxFn {
581    pub fn new(
582        name: Option<Arc<str>>,
583        arities: Vec<CljxFnArity>,
584        closed_over_names: Vec<Arc<str>>,
585        closed_over_vals: Vec<Value>,
586        is_macro: bool,
587        defining_ns: Arc<str>,
588    ) -> Self {
589        Self {
590            name,
591            arities,
592            closed_over_names,
593            closed_over_vals,
594            is_macro,
595            is_async: false,
596            defining_ns,
597        }
598    }
599}
600
601impl cljrs_gc::Trace for CljxFn {
602    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
603        for v in &self.closed_over_vals {
604            v.trace(visitor);
605        }
606    }
607
608    fn gc_size_extra(&self) -> usize {
609        // Vec<CljxFnArity> buffer + each arity's inline-owned heap
610        self.arities.capacity() * mem::size_of::<CljxFnArity>()
611            + self
612                .arities
613                .iter()
614                .map(CljxFnArity::heap_size)
615                .sum::<usize>()
616    }
617}
618
619// ── BoundFn ──────────────────────────────────────────────────────────────────
620
621/// A function wrapped with captured dynamic bindings.
622/// When called, the captured bindings are pushed as a frame before delegating
623/// to the wrapped function. This means captured bindings override the caller's
624/// for the same var, but vars not in the capture fall through normally.
625#[derive(Debug)]
626pub struct BoundFn {
627    /// The wrapped callable.
628    pub wrapped: Value,
629    /// Captured dynamic bindings (merged flat frame; opaque to cljrs-value).
630    pub captured_bindings: HashMap<usize, Value>,
631}
632
633impl cljrs_gc::Trace for BoundFn {
634    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
635        self.wrapped.trace(visitor);
636        for val in self.captured_bindings.values() {
637            val.trace(visitor);
638        }
639    }
640
641    fn gc_size_extra(&self) -> usize {
642        // HashMap<usize, Value>: hashbrown open-addressing, ~1 control byte + entry per slot.
643        self.captured_bindings.capacity() * (1 + mem::size_of::<usize>() + mem::size_of::<Value>())
644    }
645}
646
647// ── Thunk / LazySeq ───────────────────────────────────────────────────────────
648
649/// A deferred computation that produces a `Value` when forced.
650pub trait Thunk: std::fmt::Debug + cljrs_gc::Trace {
651    fn force(&self) -> Result<Value, String>;
652}
653
654/// Internal state of a lazy sequence cell.
655pub enum LazySeqState {
656    /// Thunk not yet evaluated.
657    Pending(Box<dyn Thunk>),
658    /// Result cached after first force.
659    Forced(Value),
660    /// Thunk evaluation failed; error message is cached.
661    Error(String),
662}
663
664/// A lazy sequence that forces its thunk exactly once and caches the result.
665pub struct LazySeq {
666    pub state: Mutex<LazySeqState>,
667}
668
669impl LazySeq {
670    pub fn new(thunk: Box<dyn Thunk>) -> Self {
671        Self {
672            state: Mutex::new(LazySeqState::Pending(thunk)),
673        }
674    }
675
676    /// Realize the sequence: force the thunk on first call, return cached value on subsequent calls.
677    /// On error, returns `Value::Nil` and caches the error (retrievable via `error()`).
678    pub fn realize(&self) -> Value {
679        let thunk = {
680            let mut guard = self.state.lock().unwrap();
681            match &*guard {
682                LazySeqState::Forced(v) => return v.clone(),
683                LazySeqState::Error(_) => return Value::Nil,
684                LazySeqState::Pending(_) => {}
685            }
686            // Replace the pending state with a temporary Forced(Nil), extract the thunk.
687            let prev = mem::replace(&mut *guard, LazySeqState::Forced(Value::Nil));
688            let LazySeqState::Pending(thunk) = prev else {
689                unreachable!("state was not Pending")
690            };
691            thunk
692            // guard dropped here — lock released before forcing
693        };
694        // Force the thunk WITHOUT holding the lock. This ensures GC's
695        // lock().unwrap() in LazySeq::trace() will not deadlock.
696        match thunk.force() {
697            Ok(result) => {
698                *self.state.lock().unwrap() = LazySeqState::Forced(result.clone());
699                result
700            }
701            Err(msg) => {
702                *self.state.lock().unwrap() = LazySeqState::Error(msg);
703                Value::Nil
704            }
705        }
706    }
707
708    /// Return the cached error message, if the thunk failed.
709    pub fn error(&self) -> Option<String> {
710        let guard = self.state.lock().unwrap();
711        if let LazySeqState::Error(e) = &*guard {
712            Some(e.clone())
713        } else {
714            None
715        }
716    }
717}
718
719impl std::fmt::Debug for LazySeq {
720    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
721        write!(f, "LazySeq(...)")
722    }
723}
724
725impl cljrs_gc::Trace for LazySeq {
726    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
727        // Safe to lock unconditionally: realize() drops the lock before entering
728        // eval (thunk.force()), so the lock is never held across a GC safepoint.
729        {
730            let state = self.state.lock().unwrap();
731            match &*state {
732                LazySeqState::Pending(thunk) => thunk.trace(visitor),
733                LazySeqState::Forced(v) => v.trace(visitor),
734                LazySeqState::Error(_) => {}
735            }
736        }
737    }
738}
739
740// ── CljxCons ──────────────────────────────────────────────────────────────────
741
742/// A lazy cons cell: head element + tail (may be a `LazySeq`, `List`, or `Nil`).
743///
744/// Used when `cons` is called with a `LazySeq` or `Cons` tail, enabling lazy
745/// sequences without eagerly realizing them.
746#[derive(Debug, Clone)]
747pub struct CljxCons {
748    pub head: Value,
749    pub tail: Value,
750}
751
752impl cljrs_gc::Trace for CljxCons {
753    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
754        self.head.trace(visitor);
755        self.tail.trace(visitor);
756    }
757}
758
759// ── Volatile ──────────────────────────────────────────────────────────────────
760
761/// Non-atomic mutable cell (single-thread performance, no CAS).
762pub struct Volatile {
763    pub value: Mutex<Value>,
764}
765
766impl Volatile {
767    pub fn new(v: Value) -> Self {
768        Self {
769            value: Mutex::new(v),
770        }
771    }
772
773    pub fn deref(&self) -> Value {
774        self.value.lock().unwrap().clone()
775    }
776
777    pub fn reset(&self, v: Value) -> Value {
778        // In no-gc debug builds: assert the new value came from the StaticArena.
779        #[cfg(all(feature = "no-gc", debug_assertions))]
780        debug_assert!(
781            value_gcptr_is_static(&v),
782            "no-gc: Volatile::reset() received a region-local value — ensure the \
783             new-value expression is inside a StaticCtxGuard (vreset! handles this)"
784        );
785        *self.value.lock().unwrap() = v.clone();
786        v
787    }
788}
789
790impl std::fmt::Debug for Volatile {
791    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
792        write!(f, "Volatile")
793    }
794}
795
796impl cljrs_gc::Trace for Volatile {
797    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
798        {
799            let value = self.value.lock().unwrap();
800            value.trace(visitor);
801        }
802    }
803}
804
805// ── Delay ─────────────────────────────────────────────────────────────────────
806
807/// Internal state of a delay cell.
808pub enum DelayState {
809    Pending(Box<dyn Thunk>),
810    Forced(Value),
811}
812
813/// A lazy one-time computation (forced at most once, result cached).
814pub struct Delay {
815    pub state: Mutex<DelayState>,
816}
817
818impl Delay {
819    pub fn new(thunk: Box<dyn Thunk>) -> Self {
820        Self {
821            state: Mutex::new(DelayState::Pending(thunk)),
822        }
823    }
824
825    /// Force the delay and cache the result.
826    /// Returns the value on success, or an error message on failure.
827    pub fn force(&self) -> Result<Value, String> {
828        let thunk = {
829            let mut guard = self.state.lock().unwrap();
830            if let DelayState::Forced(v) = &*guard {
831                return Ok(v.clone());
832            }
833            let prev = mem::replace(&mut *guard, DelayState::Forced(Value::Nil));
834            let DelayState::Pending(thunk) = prev else {
835                unreachable!("state was not Pending")
836            };
837            thunk
838            // guard dropped here — lock released before forcing
839        };
840        // Force the thunk WITHOUT holding the lock so GC's lock().unwrap() in
841        // Delay::trace() will not deadlock.
842        let result = thunk.force()?;
843        *self.state.lock().unwrap() = DelayState::Forced(result.clone());
844        Ok(result)
845    }
846
847    /// True if the delay has already been forced.
848    pub fn is_realized(&self) -> bool {
849        matches!(&*self.state.lock().unwrap(), DelayState::Forced(_))
850    }
851}
852
853impl std::fmt::Debug for Delay {
854    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
855        write!(f, "Delay")
856    }
857}
858
859impl cljrs_gc::Trace for Delay {
860    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
861        // Safe to lock unconditionally: force() drops the lock before entering
862        // eval (thunk.force()), so the lock is never held across a GC safepoint.
863        {
864            let state = self.state.lock().unwrap();
865            match &*state {
866                DelayState::Pending(thunk) => thunk.trace(visitor),
867                DelayState::Forced(v) => v.trace(visitor),
868            }
869        }
870    }
871}
872
873// ── CljxPromise ───────────────────────────────────────────────────────────────
874
875/// A one-shot rendezvous (promise).
876pub struct CljxPromise {
877    pub value: Mutex<Option<Value>>,
878    pub cond: Condvar,
879}
880
881impl CljxPromise {
882    pub fn new() -> Self {
883        Self {
884            value: Mutex::new(None),
885            cond: Condvar::new(),
886        }
887    }
888
889    /// Deliver a value (no-op if already delivered).
890    pub fn deliver(&self, v: Value) {
891        let mut guard = self.value.lock().unwrap();
892        if guard.is_none() {
893            *guard = Some(v);
894            self.cond.notify_all();
895        }
896    }
897
898    /// Block until a value is available, then return it.
899    pub fn deref_blocking(&self) -> Value {
900        let mut guard = self.value.lock().unwrap();
901        while guard.is_none() {
902            guard = self.cond.wait(guard).unwrap();
903        }
904        guard.as_ref().unwrap().clone()
905    }
906
907    /// True if already delivered.
908    pub fn is_realized(&self) -> bool {
909        self.value.lock().unwrap().is_some()
910    }
911}
912
913impl Default for CljxPromise {
914    fn default() -> Self {
915        Self::new()
916    }
917}
918
919impl std::fmt::Debug for CljxPromise {
920    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
921        write!(f, "Promise")
922    }
923}
924
925impl cljrs_gc::Trace for CljxPromise {
926    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
927        {
928            let value = self.value.lock().unwrap();
929            if let Some(v) = value.as_ref() {
930                v.trace(visitor);
931            }
932        }
933    }
934}
935
936// ── CljxFuture ────────────────────────────────────────────────────────────────
937
938/// Thread-pool future state.
939pub enum FutureState {
940    Running,
941    Done(Value),
942    /// The future's body threw. Holds the thrown Clojure value (a
943    /// `Value::Error`) so `await`/`deref` can re-throw it with its
944    /// `ex-data`/`ex-cause` intact, rather than a stringified message.
945    Failed(Value),
946    Cancelled,
947}
948
949/// A future value computed asynchronously on another thread.
950pub struct CljxFuture {
951    pub state: Mutex<FutureState>,
952    pub cond: Condvar,
953    /// Set once a consumer has read the settled result (via `await`/`deref`).
954    /// Used to warn about a `Failed` future that is discarded without anyone
955    /// ever observing its error (the fire-and-forget footgun).
956    observed: std::sync::atomic::AtomicBool,
957}
958
959impl CljxFuture {
960    pub fn new() -> Self {
961        Self {
962            state: Mutex::new(FutureState::Running),
963            cond: Condvar::new(),
964            observed: std::sync::atomic::AtomicBool::new(false),
965        }
966    }
967
968    /// True if done, failed, or cancelled (not still running).
969    pub fn is_done(&self) -> bool {
970        !matches!(&*self.state.lock().unwrap(), FutureState::Running)
971    }
972
973    /// True if explicitly cancelled.
974    pub fn is_cancelled(&self) -> bool {
975        matches!(&*self.state.lock().unwrap(), FutureState::Cancelled)
976    }
977
978    /// Mark this future's result as observed. Call when a consumer reads the
979    /// settled value (`await`/`deref`), so a later drop doesn't warn about an
980    /// unobserved error.
981    pub fn mark_observed(&self) {
982        self.observed
983            .store(true, std::sync::atomic::Ordering::Relaxed);
984    }
985}
986
987impl Drop for CljxFuture {
988    fn drop(&mut self) {
989        // Warn if a future failed but nobody ever observed the error — the
990        // fire-and-forget case where a thrown error would otherwise vanish.
991        // Tied to GC sweep timing: only fires once the future is unreachable,
992        // so a not-yet-awaited (still reachable) failed future won't warn.
993        //
994        // SAFETY: this Drop can run during GC sweep. The thrown value held in
995        // `Failed(v)` is itself a GC value whose backing box may be freed in
996        // the *same* sweep, so we must NOT dereference it here (no `{v}`). We
997        // only inspect the state discriminant, which is inline in our own
998        // (still-valid) allocation.
999        if !self.observed.load(std::sync::atomic::Ordering::Relaxed)
1000            && let Ok(state) = self.state.lock()
1001            && matches!(&*state, FutureState::Failed(_))
1002        {
1003            eprintln!(
1004                "[clojurust warning] a failed future was discarded without its error \
1005                 being observed (no await/deref); the thrown exception was lost"
1006            );
1007        }
1008    }
1009}
1010
1011impl Default for CljxFuture {
1012    fn default() -> Self {
1013        Self::new()
1014    }
1015}
1016
1017impl std::fmt::Debug for CljxFuture {
1018    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1019        write!(f, "Future")
1020    }
1021}
1022
1023impl cljrs_gc::Trace for CljxFuture {
1024    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1025        {
1026            let state = self.state.lock().unwrap();
1027            // Both Done and Failed hold a Value (the result or the thrown
1028            // error); trace either so the GC keeps it alive until observed.
1029            if let FutureState::Done(v) | FutureState::Failed(v) = &*state {
1030                v.trace(visitor);
1031            }
1032        }
1033    }
1034}
1035
1036// ── Agent ─────────────────────────────────────────────────────────────────────
1037
1038/// A Clojure agent — asynchronous state update queue (stub: not yet implemented).
1039pub struct Agent {
1040    /// Current state.
1041    pub state: Arc<Mutex<Value>>,
1042    /// Last error.
1043    pub error: Arc<Mutex<Option<Value>>>,
1044    pub watches: Mutex<Vec<(Value, Value)>>,
1045}
1046
1047impl Agent {
1048    pub fn get_state(&self) -> Value {
1049        self.state.lock().unwrap().clone()
1050    }
1051
1052    pub fn get_error(&self) -> Option<Value> {
1053        self.error.lock().unwrap().clone()
1054    }
1055
1056    pub fn clear_error(&self) {
1057        *self.error.lock().unwrap() = None;
1058    }
1059}
1060
1061impl std::fmt::Debug for Agent {
1062    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1063        write!(f, "Agent")
1064    }
1065}
1066
1067impl cljrs_gc::Trace for Agent {
1068    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1069        {
1070            let state = self.state.lock().unwrap();
1071            state.trace(visitor);
1072        }
1073        {
1074            let error = self.error.lock().unwrap();
1075            if let Some(e) = error.as_ref() {
1076                e.trace(visitor);
1077            }
1078        }
1079        {
1080            let watches = self.watches.lock().unwrap();
1081            for (key, f) in watches.iter() {
1082                key.trace(visitor);
1083                f.trace(visitor);
1084            }
1085        }
1086    }
1087}