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