Skip to main content

cljrs_value/
clone.rs

1//! Structured-clone boundary between isolates (Phase B2).
2//!
3//! `serialize` converts a `Value` to a `Send + Sync` intermediate form;
4//! `deserialize` allocates a fresh copy into the *current* isolate's GC heap.
5//! Non-shareable values (mutable state, closures, native resources) produce a
6//! [`CloneError`] so the compiler — not a runtime panic — enforces the boundary.
7//!
8//! The round-trip is:
9//!
10//! ```text
11//! isolate A: Value → serialize → SerializedValue   (Send) ──► thread boundary
12//! isolate B:                      SerializedValue → deserialize → Value
13//! ```
14//!
15//! ## What is shareable
16//!
17//! - All scalar immediates: `Nil`, `Bool`, `Long`, `Double`, `Char`, `Uuid`
18//! - Heap-allocated *data* values: `Str`, `BigInt`, `BigDecimal`, `Ratio`,
19//!   `Pattern` (source only), `Symbol`, `Keyword`
20//! - All persistent collections: `List`, `Vector`, `Map`, `Set`, `Queue`, `Cons`
21//! - Primitive and object arrays (snapshot of current contents)
22//! - `TypeInstance` records (fields cloned recursively)
23//! - `Error` (message + data + cause chain, `Thrown` value cloned recursively)
24//! - Lazy sequences: **realized** first; the realized value is then cloned
25//! - `WithMeta`, `Reduced` wrappers (inner value + meta cloned recursively)
26//!
27//! ## Cross-isolate shared references (Phase B3)
28//!
29//! - `SharedAtom`, `ByteBlob`  — `Arc`-cloned, not deep-copied; both isolates
30//!   share the same underlying cell/buffer.
31//! - `Var`  — the var's *root* binding crosses through its shared cell
32//!   (`Arc<ArcSwap<Option<SharedValue>>>`), so a var `def`'d in one isolate is
33//!   observable by value from another, keyword/symbol identity preserved.  A
34//!   var whose current root holds a non-promotable value (a closure / native
35//!   resource) is **not** shareable and returns `CloneError` — such vars are
36//!   explicitly isolate-local (option (b) of the ADR).
37//!
38//! ## What is *not* shareable (returns `CloneError`)
39//!
40//! - `Atom`, `Volatile`, `Promise`, `Future`, `Agent`  (mutable state)
41//! - `Fn`, `BoundFn`, `Macro`, `NativeFunction`, `ProtocolFn`, `MultiFn`
42//!   (closures capture isolate-local `GcPtr`s)
43//! - `Namespace`, `Protocol`  (global singletons managed elsewhere)
44//! - `Resource`, `NativeObject`  (isolate-bound OS handles / native objects)
45//! - `TransientMap`, `TransientSet`, `TransientVector`  (isolate-local transients)
46//! - `Delay` whose thunk has not yet been forced  (thunk is isolate-local)
47//! - `Matcher`  (regex engine state tied to one execution context)
48
49use std::sync::Arc;
50
51use arc_swap::ArcSwap;
52use num_bigint::BigInt;
53
54use crate::collections::{PersistentHashSet, PersistentVector, SortedMap, SortedSet};
55use crate::error::ValueError;
56use crate::shared::{SharedAtom, SharedValue};
57use crate::types::DelayState;
58use crate::{Keyword, MapValue, PersistentList, PersistentQueue, SetValue, Symbol, Value};
59use cljrs_gc::GcPtr;
60
61// ── Error ────────────────────────────────────────────────────────────────────
62
63/// Reason a value cannot cross an isolate boundary.
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum CloneError {
66    /// The value holds isolate-local state that cannot be serialized.
67    NotShareable {
68        /// Clojure type name (matches `Value::type_name()`).
69        type_name: &'static str,
70    },
71    /// The channel's receiver side has been dropped.
72    Disconnected,
73}
74
75impl std::fmt::Display for CloneError {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        match self {
78            CloneError::NotShareable { type_name } => {
79                write!(
80                    f,
81                    "value of type `{type_name}` cannot cross an isolate boundary"
82                )
83            }
84            CloneError::Disconnected => write!(f, "isolate channel is disconnected"),
85        }
86    }
87}
88
89impl std::error::Error for CloneError {}
90
91fn not_shareable(type_name: &'static str) -> CloneError {
92    CloneError::NotShareable { type_name }
93}
94
95// ── Wire form ─────────────────────────────────────────────────────────────────
96
97/// Send + Sync intermediate form produced by `serialize` and consumed by
98/// `deserialize`. All heap data is owned (no `GcPtr`), so it is safe to move
99/// across thread boundaries.
100#[derive(Clone, Debug)]
101pub enum SerializedValue {
102    // Scalars
103    Nil,
104    Bool(bool),
105    Long(i64),
106    Double(f64),
107    BigInt(BigInt),
108    BigDecimal(bigdecimal::BigDecimal),
109    Ratio(num_rational::Ratio<BigInt>),
110    Char(char),
111    Str(String),
112    Uuid(u128),
113    /// Regex stored as source string; recompiled on deserialize.
114    Pattern(String),
115
116    // Identifiers
117    Symbol {
118        namespace: Option<Arc<str>>,
119        name: Arc<str>,
120        version: Option<Arc<str>>,
121    },
122    Keyword {
123        namespace: Option<Arc<str>>,
124        name: Arc<str>,
125    },
126
127    // Collections
128    List(Vec<SerializedValue>),
129    Vector(Vec<SerializedValue>),
130    ArrayMap(Vec<(SerializedValue, SerializedValue)>),
131    HashMap(Vec<(SerializedValue, SerializedValue)>),
132    SortedMap(Vec<(SerializedValue, SerializedValue)>),
133    HashSet(Vec<SerializedValue>),
134    SortedSet(Vec<SerializedValue>),
135    Queue(Vec<SerializedValue>),
136    Cons {
137        head: Box<SerializedValue>,
138        tail: Box<SerializedValue>,
139    },
140
141    // Records
142    TypeInstance {
143        type_tag: Arc<str>,
144        fields: Vec<(SerializedValue, SerializedValue)>,
145    },
146
147    // Errors
148    Error(Box<SerializedError>),
149
150    // Primitive arrays (snapshot of current contents)
151    BooleanArray(Vec<bool>),
152    ByteArray(Vec<i8>),
153    ShortArray(Vec<i16>),
154    IntArray(Vec<i32>),
155    LongArray(Vec<i64>),
156    FloatArray(Vec<f32>),
157    DoubleArray(Vec<f64>),
158    CharArray(Vec<char>),
159    ObjectArray(Vec<SerializedValue>),
160
161    // Wrappers
162    WithMeta {
163        value: Box<SerializedValue>,
164        meta: Box<SerializedValue>,
165    },
166    Reduced(Box<SerializedValue>),
167
168    // Phase B3: cross-isolate shared references (Arc cloned, not deep-copied).
169    /// `SharedAtom` is inherently cross-isolate; the `Arc` is simply cloned so
170    /// both isolates share the same underlying `ArcSwap` cell.
171    SharedAtom(Arc<SharedAtom>),
172    /// `ByteBlob` is an immutable refcounted buffer; clone the `Arc`.
173    ByteBlob(Arc<[u8]>),
174    /// A var crosses by sharing its cross-isolate root cell (the `Arc` is
175    /// cloned, so both isolates point at the same `ArcSwap`).  The receiving
176    /// isolate rebuilds a `Var` whose local fast-path slot is the demoted
177    /// current snapshot.  Only vars with a promotable (or unbound) root reach
178    /// here — a non-promotable root is rejected at `serialize` time.
179    Var {
180        namespace: Arc<str>,
181        name: Arc<str>,
182        is_macro: bool,
183        shared_root: Arc<ArcSwap<Option<SharedValue>>>,
184    },
185}
186
187// Compile-time Send + Sync assertions.
188const _: () = {
189    const fn _assert_send<T: Send + Sync>() {}
190    let _ = _assert_send::<SerializedValue>;
191};
192
193impl SerializedValue {
194    /// Estimated heap bytes materialized by deep-copying this value into the
195    /// receiving isolate's heap. This is an approximation for **telemetry**
196    /// (the metered clone seam the isolate-boundary plan requires), not an
197    /// exact allocation count: each node contributes a fixed per-node cost plus
198    /// the size of any owned payload (string bytes, array elements, big-number
199    /// magnitude). `Arc`-shared payloads (`SharedAtom`, `ByteBlob`) count as
200    /// zero structural bytes because they cross by refcount, not by copy.
201    pub fn byte_size(&self) -> usize {
202        // Per-node base cost: one `SerializedValue` slot plus the GcPtr/box the
203        // deserialized form allocates on the receiver side.
204        const NODE: usize = std::mem::size_of::<SerializedValue>();
205
206        let payload = match self {
207            SerializedValue::Nil
208            | SerializedValue::Bool(_)
209            | SerializedValue::Long(_)
210            | SerializedValue::Double(_)
211            | SerializedValue::Char(_)
212            | SerializedValue::Uuid(_) => 0,
213
214            SerializedValue::BigInt(b) => (b.bits() as usize / 8) + 1,
215            SerializedValue::Ratio(r) => {
216                (r.numer().bits() as usize + r.denom().bits() as usize) / 8 + 2
217            }
218            SerializedValue::BigDecimal(_) => 16,
219
220            SerializedValue::Str(s) => s.len(),
221            SerializedValue::Pattern(s) => s.len(),
222
223            SerializedValue::Symbol {
224                namespace, name, ..
225            } => namespace.as_ref().map_or(0, |n| n.len()) + name.len(),
226            SerializedValue::Keyword { namespace, name } => {
227                namespace.as_ref().map_or(0, |n| n.len()) + name.len()
228            }
229
230            SerializedValue::List(items)
231            | SerializedValue::Vector(items)
232            | SerializedValue::HashSet(items)
233            | SerializedValue::SortedSet(items)
234            | SerializedValue::Queue(items)
235            | SerializedValue::ObjectArray(items) => {
236                items.iter().map(SerializedValue::byte_size).sum()
237            }
238
239            SerializedValue::ArrayMap(pairs)
240            | SerializedValue::HashMap(pairs)
241            | SerializedValue::SortedMap(pairs) => pairs
242                .iter()
243                .map(|(k, v)| k.byte_size() + v.byte_size())
244                .sum(),
245
246            SerializedValue::TypeInstance { fields, .. } => fields
247                .iter()
248                .map(|(k, v)| k.byte_size() + v.byte_size())
249                .sum(),
250
251            SerializedValue::Cons { head, tail } => head.byte_size() + tail.byte_size(),
252
253            SerializedValue::Error(e) => e.byte_size(),
254
255            SerializedValue::BooleanArray(v) => v.len(),
256            SerializedValue::ByteArray(v) => v.len(),
257            SerializedValue::ShortArray(v) => std::mem::size_of_val(v.as_slice()),
258            SerializedValue::IntArray(v) => std::mem::size_of_val(v.as_slice()),
259            SerializedValue::LongArray(v) => std::mem::size_of_val(v.as_slice()),
260            SerializedValue::FloatArray(v) => std::mem::size_of_val(v.as_slice()),
261            SerializedValue::DoubleArray(v) => std::mem::size_of_val(v.as_slice()),
262            SerializedValue::CharArray(v) => std::mem::size_of_val(v.as_slice()),
263
264            SerializedValue::WithMeta { value, meta } => value.byte_size() + meta.byte_size(),
265            SerializedValue::Reduced(inner) => inner.byte_size(),
266
267            // Arc-shared: crosses by refcount bump, no structural copy.
268            SerializedValue::SharedAtom(_) | SerializedValue::ByteBlob(_) => 0,
269
270            // The var's root cell is Arc-shared; only the ns/name strings copy.
271            SerializedValue::Var {
272                namespace, name, ..
273            } => namespace.len() + name.len(),
274        };
275
276        NODE + payload
277    }
278}
279
280impl SerializedError {
281    fn byte_size(&self) -> usize {
282        std::mem::size_of::<SerializedError>()
283            + self.message.len()
284            + self.data.as_ref().map_or(0, |pairs| {
285                pairs
286                    .iter()
287                    .map(|(k, v)| k.byte_size() + v.byte_size())
288                    .sum()
289            })
290            + self.cause.as_ref().map_or(0, |c| c.byte_size())
291    }
292}
293
294/// Serialized form of [`crate::error::ExceptionInfo`].
295#[derive(Clone, Debug)]
296pub struct SerializedError {
297    pub kind: SerializedErrorKind,
298    pub message: String,
299    pub data: Option<Vec<(SerializedValue, SerializedValue)>>,
300    pub cause: Option<Box<SerializedError>>,
301}
302
303/// Mirrors [`ValueError`] with `Value` replaced by `SerializedValue`.
304#[derive(Clone, Debug)]
305pub enum SerializedErrorKind {
306    WrongType {
307        expected: &'static str,
308        got: String,
309    },
310    IndexOutOfBounds {
311        idx: usize,
312        count: usize,
313    },
314    ArityError {
315        name: String,
316        expected: String,
317        got: usize,
318    },
319    NotCallable {
320        value: String,
321    },
322    OddMap {
323        count: usize,
324    },
325    Unsupported,
326    Other(String),
327    OutOfRange,
328    TransientAlreadyPersisted,
329    Parse,
330    Thrown(Box<SerializedValue>),
331}
332
333// ── serialize ─────────────────────────────────────────────────────────────────
334
335/// Serialize a `Value` into a `Send + Sync` wire form suitable for crossing an
336/// isolate boundary. Returns [`CloneError`] for non-shareable values.
337pub fn serialize(v: &Value) -> Result<SerializedValue, CloneError> {
338    match v {
339        // ── Wrappers ──
340        Value::WithMeta(inner, meta) => Ok(SerializedValue::WithMeta {
341            value: Box::new(serialize(inner)?),
342            meta: Box::new(serialize(meta)?),
343        }),
344        Value::Reduced(inner) => Ok(SerializedValue::Reduced(Box::new(serialize(inner)?))),
345
346        // ── Scalars ──
347        Value::Nil => Ok(SerializedValue::Nil),
348        Value::Bool(b) => Ok(SerializedValue::Bool(*b)),
349        Value::Long(n) => Ok(SerializedValue::Long(*n)),
350        Value::Double(d) => Ok(SerializedValue::Double(*d)),
351        Value::Char(c) => Ok(SerializedValue::Char(*c)),
352        Value::Uuid(u) => Ok(SerializedValue::Uuid(*u)),
353
354        Value::BigInt(p) => Ok(SerializedValue::BigInt(p.get().clone())),
355        Value::BigDecimal(p) => Ok(SerializedValue::BigDecimal(p.get().clone())),
356        Value::Ratio(p) => Ok(SerializedValue::Ratio(p.get().clone())),
357        Value::Str(p) => Ok(SerializedValue::Str(p.get().clone())),
358        Value::Pattern(p) => Ok(SerializedValue::Pattern(p.get().as_str().to_owned())),
359
360        // ── Identifiers ──
361        Value::Symbol(p) => {
362            let s = p.get();
363            Ok(SerializedValue::Symbol {
364                namespace: s.namespace.clone(),
365                name: s.name.clone(),
366                version: s.version.clone(),
367            })
368        }
369        Value::Keyword(p) => {
370            let k = p.get();
371            Ok(SerializedValue::Keyword {
372                namespace: k.namespace.clone(),
373                name: k.name.clone(),
374            })
375        }
376
377        // ── Collections ──
378        Value::List(p) => {
379            let items: Result<Vec<_>, _> = p.get().iter().map(serialize).collect();
380            Ok(SerializedValue::List(items?))
381        }
382        Value::Vector(p) => {
383            let items: Result<Vec<_>, _> = p.get().iter().map(serialize).collect();
384            Ok(SerializedValue::Vector(items?))
385        }
386        Value::Map(m) => serialize_map(m),
387        Value::Set(s) => serialize_set(s),
388        Value::Queue(p) => {
389            let items: Result<Vec<_>, _> = p.get().iter().map(serialize).collect();
390            Ok(SerializedValue::Queue(items?))
391        }
392        Value::Cons(p) => {
393            let c = p.get();
394            Ok(SerializedValue::Cons {
395                head: Box::new(serialize(&c.head)?),
396                tail: Box::new(serialize(&c.tail)?),
397            })
398        }
399
400        // ── Records ──
401        Value::TypeInstance(p) => {
402            let ti = p.get();
403            let fields = serialize_map_pairs(&ti.fields)?;
404            Ok(SerializedValue::TypeInstance {
405                type_tag: ti.type_tag.clone(),
406                fields,
407            })
408        }
409
410        // ── Errors ──
411        Value::Error(p) => Ok(SerializedValue::Error(Box::new(serialize_error(p.get())?))),
412
413        // ── Primitive arrays (snapshot) ──
414        Value::BooleanArray(p) => Ok(SerializedValue::BooleanArray(
415            p.get().lock().unwrap().clone(),
416        )),
417        Value::ByteArray(p) => Ok(SerializedValue::ByteArray(p.get().lock().unwrap().clone())),
418        Value::ShortArray(p) => Ok(SerializedValue::ShortArray(p.get().lock().unwrap().clone())),
419        Value::IntArray(p) => Ok(SerializedValue::IntArray(p.get().lock().unwrap().clone())),
420        Value::LongArray(p) => Ok(SerializedValue::LongArray(p.get().lock().unwrap().clone())),
421        Value::FloatArray(p) => Ok(SerializedValue::FloatArray(p.get().lock().unwrap().clone())),
422        Value::DoubleArray(p) => Ok(SerializedValue::DoubleArray(
423            p.get().lock().unwrap().clone(),
424        )),
425        Value::CharArray(p) => Ok(SerializedValue::CharArray(p.get().lock().unwrap().clone())),
426        Value::ObjectArray(p) => {
427            let guard = p.get().0.lock().unwrap();
428            let items: Result<Vec<_>, _> = guard.iter().map(serialize).collect();
429            Ok(SerializedValue::ObjectArray(items?))
430        }
431
432        // ── Lazy sequences: realize first ──
433        Value::LazySeq(p) => serialize(&p.get().realize()),
434
435        // ── Delay: force if already realized, else error ──
436        Value::Delay(p) => {
437            let state = p.get().state.lock().unwrap();
438            if let DelayState::Forced(v) = &*state {
439                serialize(v)
440            } else {
441                Err(not_shareable("delay"))
442            }
443        }
444
445        // ── Non-shareable ──
446        Value::Resource(_) => Err(not_shareable("resource")),
447        Value::NativeObject(_) => Err(not_shareable("native-object")),
448        Value::Matcher(_) => Err(not_shareable("matcher")),
449
450        Value::Fn(_) | Value::Macro(_) => Err(not_shareable("fn")),
451        Value::BoundFn(_) => Err(not_shareable("fn")),
452        Value::NativeFunction(_) => Err(not_shareable("fn")),
453        Value::ProtocolFn(_) => Err(not_shareable("fn")),
454        Value::MultiFn(_) => Err(not_shareable("fn")),
455
456        // ── Phase B3: vars cross by sharing their root cell ──
457        Value::Var(p) => {
458            let var = p.get();
459            // The shared cell is kept in sync by `Var::bind` (promote-on-def).
460            // It is empty when the var is unbound *or* its current root is not
461            // promotable.  Distinguish the two: an unbound var may cross (it
462            // arrives unbound), but a var bound to a non-promotable value
463            // (closure / native resource) is explicitly isolate-local and is
464            // rejected here — a non-silent boundary error, not a silent drop.
465            let shared_empty = var.shared_root.load().is_none();
466            if shared_empty && var.is_bound() {
467                return Err(not_shareable("var"));
468            }
469            Ok(SerializedValue::Var {
470                namespace: var.namespace.clone(),
471                name: var.name.clone(),
472                is_macro: var.is_macro,
473                shared_root: var.shared_root.clone(),
474            })
475        }
476        Value::Atom(_) => Err(not_shareable("atom")),
477        Value::Volatile(_) => Err(not_shareable("volatile")),
478        Value::Promise(_) => Err(not_shareable("promise")),
479        Value::Future(_) => Err(not_shareable("future")),
480        Value::Agent(_) => Err(not_shareable("agent")),
481
482        Value::Namespace(_) => Err(not_shareable("namespace")),
483        Value::Protocol(_) => Err(not_shareable("protocol")),
484
485        Value::TransientMap(_) => Err(not_shareable("transient-map")),
486        Value::TransientSet(_) => Err(not_shareable("transient-set")),
487        Value::TransientVector(_) => Err(not_shareable("transient-vector")),
488
489        // ── Phase B3: cross-isolate shared references (pass Arc through) ──
490        Value::SharedAtom(a) => Ok(SerializedValue::SharedAtom(a.clone())),
491        Value::ByteBlob(b) => Ok(SerializedValue::ByteBlob(b.clone())),
492    }
493}
494
495fn serialize_map(m: &MapValue) -> Result<SerializedValue, CloneError> {
496    let pairs = serialize_map_pairs(m)?;
497    Ok(match m {
498        MapValue::Array(_) => SerializedValue::ArrayMap(pairs),
499        MapValue::Hash(_) => SerializedValue::HashMap(pairs),
500        MapValue::Sorted(_) => SerializedValue::SortedMap(pairs),
501    })
502}
503
504fn serialize_map_pairs(
505    m: &MapValue,
506) -> Result<Vec<(SerializedValue, SerializedValue)>, CloneError> {
507    let mut pairs = Vec::with_capacity(m.count());
508    let mut err: Option<CloneError> = None;
509    m.for_each(|k, v| {
510        if err.is_some() {
511            return;
512        }
513        match (serialize(k), serialize(v)) {
514            (Ok(sk), Ok(sv)) => pairs.push((sk, sv)),
515            (Err(e), _) | (_, Err(e)) => err = Some(e),
516        }
517    });
518    if let Some(e) = err { Err(e) } else { Ok(pairs) }
519}
520
521fn serialize_set(s: &SetValue) -> Result<SerializedValue, CloneError> {
522    let items: Result<Vec<_>, _> = s.iter().map(serialize).collect();
523    Ok(match s {
524        SetValue::Hash(_) => SerializedValue::HashSet(items?),
525        SetValue::Sorted(_) => SerializedValue::SortedSet(items?),
526    })
527}
528
529fn serialize_error(e: &crate::error::ExceptionInfo) -> Result<SerializedError, CloneError> {
530    let kind = match &e.error {
531        ValueError::WrongType { expected, got } => SerializedErrorKind::WrongType {
532            expected,
533            got: got.clone(),
534        },
535        ValueError::IndexOutOfBounds { idx, count } => SerializedErrorKind::IndexOutOfBounds {
536            idx: *idx,
537            count: *count,
538        },
539        ValueError::ArityError {
540            name,
541            expected,
542            got,
543        } => SerializedErrorKind::ArityError {
544            name: name.clone(),
545            expected: expected.clone(),
546            got: *got,
547        },
548        ValueError::NotCallable { value } => SerializedErrorKind::NotCallable {
549            value: value.clone(),
550        },
551        ValueError::OddMap { count } => SerializedErrorKind::OddMap { count: *count },
552        ValueError::Unsupported => SerializedErrorKind::Unsupported,
553        ValueError::Other(s) => SerializedErrorKind::Other(s.clone()),
554        ValueError::OutOfRange => SerializedErrorKind::OutOfRange,
555        ValueError::TransientAlreadyPersisted => SerializedErrorKind::TransientAlreadyPersisted,
556        ValueError::Parse => SerializedErrorKind::Parse,
557        ValueError::Thrown(v) => SerializedErrorKind::Thrown(Box::new(serialize(v)?)),
558    };
559
560    let data = e.data.as_ref().map(serialize_map_pairs).transpose()?;
561
562    let cause = e
563        .cause
564        .as_ref()
565        .map(|c| serialize_error(c.get()).map(Box::new))
566        .transpose()?;
567
568    Ok(SerializedError {
569        kind,
570        message: e.message.clone(),
571        data,
572        cause,
573    })
574}
575
576// ── deserialize ───────────────────────────────────────────────────────────────
577
578/// Deserialize a wire form into a fresh `Value` allocated in the *current*
579/// isolate's GC heap. Infallible: all non-shareable values are rejected at
580/// `serialize` time, so nothing in `SerializedValue` requires runtime checks.
581pub fn deserialize(sv: SerializedValue) -> Value {
582    match sv {
583        SerializedValue::WithMeta { value, meta } => {
584            Value::WithMeta(Box::new(deserialize(*value)), Box::new(deserialize(*meta)))
585        }
586        SerializedValue::Reduced(inner) => Value::Reduced(Box::new(deserialize(*inner))),
587
588        SerializedValue::Nil => Value::Nil,
589        SerializedValue::Bool(b) => Value::Bool(b),
590        SerializedValue::Long(n) => Value::Long(n),
591        SerializedValue::Double(d) => Value::Double(d),
592        SerializedValue::Char(c) => Value::Char(c),
593        SerializedValue::Uuid(u) => Value::Uuid(u),
594
595        SerializedValue::BigInt(n) => Value::BigInt(GcPtr::new(n)),
596        SerializedValue::BigDecimal(d) => Value::BigDecimal(GcPtr::new(d)),
597        SerializedValue::Ratio(r) => Value::Ratio(GcPtr::new(r)),
598        SerializedValue::Str(s) => Value::Str(GcPtr::new(s)),
599        SerializedValue::Pattern(src) => Value::Pattern(GcPtr::new(
600            regex::Regex::new(&src).expect("pattern was valid at serialize time"),
601        )),
602
603        SerializedValue::Symbol {
604            namespace,
605            name,
606            version,
607        } => Value::Symbol(GcPtr::new(Symbol {
608            namespace,
609            name,
610            version,
611        })),
612        SerializedValue::Keyword { namespace, name } => {
613            Value::Keyword(GcPtr::new(Keyword { namespace, name }))
614        }
615
616        SerializedValue::List(items) => Value::List(GcPtr::new(PersistentList::from_iter(
617            items.into_iter().map(deserialize),
618        ))),
619        SerializedValue::Vector(items) => Value::Vector(GcPtr::new(PersistentVector::from_iter(
620            items.into_iter().map(deserialize),
621        ))),
622        SerializedValue::ArrayMap(pairs) => Value::Map(MapValue::from_pairs(
623            pairs
624                .into_iter()
625                .map(|(k, v)| (deserialize(k), deserialize(v)))
626                .collect(),
627        )),
628        SerializedValue::HashMap(pairs) => Value::Map(MapValue::from_pairs(
629            pairs
630                .into_iter()
631                .map(|(k, v)| (deserialize(k), deserialize(v)))
632                .collect(),
633        )),
634        SerializedValue::SortedMap(pairs) => {
635            // Rebuild as a sorted map through the standard sorted-map path.
636            let items: Vec<(Value, Value)> = pairs
637                .into_iter()
638                .map(|(k, v)| (deserialize(k), deserialize(v)))
639                .collect();
640            let sm = SortedMap::from_pairs(items);
641            Value::Map(MapValue::Sorted(GcPtr::new(sm)))
642        }
643        SerializedValue::HashSet(items) => {
644            let mut hs = PersistentHashSet::empty();
645            for item in items.into_iter().map(deserialize) {
646                hs = hs.conj(item);
647            }
648            Value::Set(SetValue::Hash(GcPtr::new(hs)))
649        }
650        SerializedValue::SortedSet(items) => {
651            let mut ss = SortedSet::empty();
652            for item in items.into_iter().map(deserialize) {
653                ss = ss.conj(item);
654            }
655            Value::Set(SetValue::Sorted(GcPtr::new(ss)))
656        }
657        SerializedValue::Queue(items) => {
658            let mut q = PersistentQueue::empty();
659            for item in items.into_iter().map(deserialize) {
660                q = q.conj(item);
661            }
662            Value::Queue(GcPtr::new(q))
663        }
664        SerializedValue::Cons { head, tail } => {
665            use crate::types::CljxCons;
666            Value::Cons(GcPtr::new(CljxCons {
667                head: deserialize(*head),
668                tail: deserialize(*tail),
669            }))
670        }
671
672        SerializedValue::TypeInstance { type_tag, fields } => {
673            use crate::value::TypeInstance;
674            let pairs: Vec<(Value, Value)> = fields
675                .into_iter()
676                .map(|(k, v)| (deserialize(k), deserialize(v)))
677                .collect();
678            Value::TypeInstance(GcPtr::new(TypeInstance {
679                type_tag,
680                fields: MapValue::from_pairs(pairs),
681            }))
682        }
683
684        SerializedValue::Error(se) => Value::Error(GcPtr::new(deserialize_error(*se))),
685
686        SerializedValue::BooleanArray(v) => {
687            Value::BooleanArray(GcPtr::new(std::sync::Mutex::new(v)))
688        }
689        SerializedValue::ByteArray(v) => Value::ByteArray(GcPtr::new(std::sync::Mutex::new(v))),
690        SerializedValue::ShortArray(v) => Value::ShortArray(GcPtr::new(std::sync::Mutex::new(v))),
691        SerializedValue::IntArray(v) => Value::IntArray(GcPtr::new(std::sync::Mutex::new(v))),
692        SerializedValue::LongArray(v) => Value::LongArray(GcPtr::new(std::sync::Mutex::new(v))),
693        SerializedValue::FloatArray(v) => Value::FloatArray(GcPtr::new(std::sync::Mutex::new(v))),
694        SerializedValue::DoubleArray(v) => Value::DoubleArray(GcPtr::new(std::sync::Mutex::new(v))),
695        SerializedValue::CharArray(v) => Value::CharArray(GcPtr::new(std::sync::Mutex::new(v))),
696        SerializedValue::ObjectArray(items) => {
697            use crate::value::ObjectArray;
698            Value::ObjectArray(GcPtr::new(ObjectArray::new(
699                items.into_iter().map(deserialize).collect(),
700            )))
701        }
702
703        // Phase B3: cross-isolate shared references — clone the Arc.
704        SerializedValue::SharedAtom(a) => Value::SharedAtom(a),
705        SerializedValue::ByteBlob(b) => Value::ByteBlob(b),
706        SerializedValue::Var {
707            namespace,
708            name,
709            is_macro,
710            shared_root,
711        } => Value::Var(GcPtr::new(crate::types::Var::from_shared_root(
712            namespace,
713            name,
714            is_macro,
715            shared_root,
716        ))),
717    }
718}
719
720fn deserialize_error(se: SerializedError) -> crate::error::ExceptionInfo {
721    let error = match se.kind {
722        SerializedErrorKind::WrongType { expected, got } => ValueError::WrongType { expected, got },
723        SerializedErrorKind::IndexOutOfBounds { idx, count } => {
724            ValueError::IndexOutOfBounds { idx, count }
725        }
726        SerializedErrorKind::ArityError {
727            name,
728            expected,
729            got,
730        } => ValueError::ArityError {
731            name,
732            expected,
733            got,
734        },
735        SerializedErrorKind::NotCallable { value } => ValueError::NotCallable { value },
736        SerializedErrorKind::OddMap { count } => ValueError::OddMap { count },
737        SerializedErrorKind::Unsupported => ValueError::Unsupported,
738        SerializedErrorKind::Other(s) => ValueError::Other(s),
739        SerializedErrorKind::OutOfRange => ValueError::OutOfRange,
740        SerializedErrorKind::TransientAlreadyPersisted => ValueError::TransientAlreadyPersisted,
741        SerializedErrorKind::Parse => ValueError::Parse,
742        SerializedErrorKind::Thrown(sv) => ValueError::Thrown(deserialize(*sv)),
743    };
744
745    let data = se.data.map(|pairs| {
746        MapValue::from_pairs(
747            pairs
748                .into_iter()
749                .map(|(k, v)| (deserialize(k), deserialize(v)))
750                .collect(),
751        )
752    });
753
754    let cause = se.cause.map(|c| GcPtr::new(deserialize_error(*c)));
755
756    crate::error::ExceptionInfo::new(error, se.message, data, cause)
757}
758
759// ── Tests ─────────────────────────────────────────────────────────────────────
760
761#[cfg(test)]
762mod tests {
763    use super::*;
764
765    fn roundtrip(v: &Value) -> Value {
766        deserialize(serialize(v).expect("serialize"))
767    }
768
769    #[test]
770    fn scalars_roundtrip() {
771        assert_eq!(roundtrip(&Value::Nil), Value::Nil);
772        assert_eq!(roundtrip(&Value::Bool(true)), Value::Bool(true));
773        assert_eq!(roundtrip(&Value::Long(42)), Value::Long(42));
774        assert_eq!(roundtrip(&Value::Char('x')), Value::Char('x'));
775        assert_eq!(roundtrip(&Value::Uuid(12345)), Value::Uuid(12345));
776    }
777
778    #[test]
779    fn string_roundtrip() {
780        let v = Value::string("hello, world");
781        assert_eq!(roundtrip(&v), v);
782    }
783
784    #[test]
785    fn keyword_roundtrip() {
786        let v = Value::keyword(Keyword::simple("foo"));
787        assert_eq!(roundtrip(&v), v);
788        let v2 = Value::keyword(Keyword::qualified("clojure.core", "map"));
789        assert_eq!(roundtrip(&v2), v2);
790    }
791
792    #[test]
793    fn symbol_roundtrip() {
794        let v = Value::symbol(Symbol::simple("my-fn"));
795        assert_eq!(roundtrip(&v), v);
796    }
797
798    #[test]
799    fn list_roundtrip() {
800        let v = Value::List(GcPtr::new(PersistentList::from_iter([
801            Value::Long(1),
802            Value::Long(2),
803            Value::Long(3),
804        ])));
805        assert_eq!(roundtrip(&v), v);
806    }
807
808    #[test]
809    fn vector_roundtrip() {
810        let v = Value::Vector(GcPtr::new(PersistentVector::from_iter([
811            Value::string("a"),
812            Value::Bool(false),
813            Value::Nil,
814        ])));
815        assert_eq!(roundtrip(&v), v);
816    }
817
818    #[test]
819    fn nested_map_roundtrip() {
820        let inner = Value::Vector(GcPtr::new(PersistentVector::from_iter([Value::Long(1)])));
821        let v = MapValue::from_pairs(vec![(Value::keyword(Keyword::simple("k")), inner)]);
822        let v = Value::Map(v);
823        assert_eq!(roundtrip(&v), v);
824    }
825
826    #[test]
827    fn resource_not_shareable() {
828        use crate::resource::ResourceHandle;
829        use std::any::Any;
830        use std::sync::Arc;
831        #[derive(Debug)]
832        struct FakeResource;
833        impl crate::resource::Resource for FakeResource {
834            fn resource_type(&self) -> &'static str {
835                "fake"
836            }
837            fn close(&self) -> crate::error::ValueResult<()> {
838                Ok(())
839            }
840            fn is_closed(&self) -> bool {
841                false
842            }
843            fn as_any(&self) -> &dyn Any {
844                self
845            }
846        }
847        let r = Value::Resource(ResourceHandle(Arc::new(FakeResource)));
848        assert!(matches!(
849            serialize(&r),
850            Err(CloneError::NotShareable {
851                type_name: "resource"
852            })
853        ));
854    }
855
856    #[test]
857    fn atom_not_shareable() {
858        use crate::types::Atom;
859        let a = Value::Atom(GcPtr::new(Atom::new(Value::Nil)));
860        assert!(matches!(
861            serialize(&a),
862            Err(CloneError::NotShareable { type_name: "atom" })
863        ));
864    }
865
866    #[test]
867    fn fn_not_shareable() {
868        use crate::types::{Arity, NativeFn};
869        let nf = Value::NativeFunction(GcPtr::new(NativeFn::new("test", Arity::Fixed(0), |_| {
870            Ok(Value::Nil)
871        })));
872        assert!(matches!(
873            serialize(&nf),
874            Err(CloneError::NotShareable { type_name: "fn" })
875        ));
876    }
877
878    #[test]
879    fn lazy_seq_realized_roundtrip() {
880        use crate::types::{LazySeq, Thunk};
881        struct DoneThunk;
882        impl std::fmt::Debug for DoneThunk {
883            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
884                write!(f, "DoneThunk")
885            }
886        }
887        impl cljrs_gc::Trace for DoneThunk {
888            fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
889        }
890        impl Thunk for DoneThunk {
891            fn force(&self) -> Result<Value, String> {
892                Ok(Value::Long(99))
893            }
894        }
895        let ls = LazySeq::new(Box::new(DoneThunk));
896        let _ = ls.realize(); // force it
897        let v = Value::LazySeq(GcPtr::new(ls));
898        assert_eq!(roundtrip(&v), Value::Long(99));
899    }
900
901    #[test]
902    fn with_meta_roundtrip() {
903        let v = Value::Long(7).with_meta(Value::Map(MapValue::empty()));
904        let rt = roundtrip(&v);
905        // WithMeta strips for equality, so unwrap and check inner
906        assert_eq!(rt, Value::Long(7));
907    }
908
909    #[test]
910    fn reduced_roundtrip() {
911        let v = Value::Reduced(Box::new(Value::Long(55)));
912        assert_eq!(roundtrip(&v), Value::Reduced(Box::new(Value::Long(55))));
913    }
914
915    #[test]
916    fn byte_size_counts_string_payload() {
917        let small = serialize(&Value::string("hi")).unwrap();
918        let large = serialize(&Value::string("x".repeat(1000))).unwrap();
919        // Same variant, so the difference is the string payload (~998 bytes).
920        assert!(large.byte_size() > small.byte_size() + 900);
921    }
922
923    #[test]
924    fn byte_size_grows_with_collection() {
925        let small = serialize(&Value::Vector(GcPtr::new(PersistentVector::from_iter([
926            Value::Long(1),
927        ]))))
928        .unwrap();
929        let large = serialize(&Value::Vector(GcPtr::new(PersistentVector::from_iter(
930            (0..100).map(Value::Long),
931        ))))
932        .unwrap();
933        assert!(large.byte_size() > small.byte_size());
934    }
935
936    #[test]
937    fn var_with_promotable_root_roundtrips() {
938        use crate::types::Var;
939        // A var def'd with a promotable value crosses by value.
940        let var = Var::new("user", "answer");
941        var.bind(Value::Long(42));
942        let v = Value::Var(GcPtr::new(var));
943
944        let crossed = roundtrip(&v);
945        if let Value::Var(p) = crossed {
946            let got = p.get();
947            assert_eq!(got.namespace.as_ref(), "user");
948            assert_eq!(got.name.as_ref(), "answer");
949            // Observable by value on the receiving side.
950            assert_eq!(got.deref(), Some(Value::Long(42)));
951        } else {
952            panic!("expected a Var on the receiving side");
953        }
954    }
955
956    #[test]
957    fn var_keyword_root_preserves_identity() {
958        use crate::types::Var;
959        let var = Var::new("user", "k");
960        var.bind(Value::keyword(Keyword::qualified("ns", "kw")));
961        let v = Value::Var(GcPtr::new(var));
962
963        let crossed = roundtrip(&v);
964        let Value::Var(p) = crossed else {
965            panic!("expected Var")
966        };
967        // Keyword identity preserved through the intern table on demote.
968        assert_eq!(
969            p.get().deref(),
970            Some(Value::keyword(Keyword::qualified("ns", "kw")))
971        );
972    }
973
974    #[test]
975    fn var_shares_root_cell_across_boundary() {
976        use crate::types::Var;
977        // Both isolates point at the *same* shared root cell, so a write on the
978        // sending side is observable through the receiver's shared view.
979        let var = GcPtr::new(Var::new("user", "shared"));
980        var.get().bind(Value::Long(1));
981        let v = Value::Var(var.clone());
982
983        let Value::Var(recv) = roundtrip(&v) else {
984            panic!("expected Var")
985        };
986        assert_eq!(recv.get().deref_shared(), Some(Value::Long(1)));
987
988        // Sender re-defs through the same cell; receiver observes via the cell.
989        var.get().bind(Value::Long(2));
990        assert_eq!(recv.get().deref_shared(), Some(Value::Long(2)));
991    }
992
993    #[test]
994    fn var_with_nonpromotable_root_is_not_shareable() {
995        use crate::types::{Arity, NativeFn, Var};
996        // A var bound to a closure / native fn is explicitly isolate-local.
997        let var = Var::new("user", "f");
998        var.bind(Value::NativeFunction(GcPtr::new(NativeFn::new(
999            "f",
1000            Arity::Fixed(0),
1001            |_| Ok(Value::Nil),
1002        ))));
1003        let v = Value::Var(GcPtr::new(var));
1004        assert!(matches!(
1005            serialize(&v),
1006            Err(CloneError::NotShareable { type_name: "var" })
1007        ));
1008    }
1009
1010    #[test]
1011    fn unbound_var_crosses_as_unbound() {
1012        use crate::types::Var;
1013        let v = Value::Var(GcPtr::new(Var::new("user", "later")));
1014        let Value::Var(p) = roundtrip(&v) else {
1015            panic!("expected Var")
1016        };
1017        assert!(!p.get().is_bound());
1018    }
1019
1020    #[test]
1021    fn byte_size_scalar_is_node_sized() {
1022        // A scalar has no owned payload, so it costs exactly one node.
1023        let node = std::mem::size_of::<SerializedValue>();
1024        assert_eq!(serialize(&Value::Long(7)).unwrap().byte_size(), node);
1025        assert_eq!(serialize(&Value::Nil).unwrap().byte_size(), node);
1026    }
1027}