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    GasExhausted,
328    OutOfRange,
329    TransientAlreadyPersisted,
330    Parse,
331    Thrown(Box<SerializedValue>),
332}
333
334// ── serialize ─────────────────────────────────────────────────────────────────
335
336/// Serialize a `Value` into a `Send + Sync` wire form suitable for crossing an
337/// isolate boundary. Returns [`CloneError`] for non-shareable values.
338pub fn serialize(v: &Value) -> Result<SerializedValue, CloneError> {
339    match v {
340        // ── Wrappers ──
341        Value::WithMeta(inner, meta) => Ok(SerializedValue::WithMeta {
342            value: Box::new(serialize(inner)?),
343            meta: Box::new(serialize(meta)?),
344        }),
345        Value::Reduced(inner) => Ok(SerializedValue::Reduced(Box::new(serialize(inner)?))),
346
347        // ── Scalars ──
348        Value::Nil => Ok(SerializedValue::Nil),
349        Value::Bool(b) => Ok(SerializedValue::Bool(*b)),
350        Value::Long(n) => Ok(SerializedValue::Long(*n)),
351        Value::Double(d) => Ok(SerializedValue::Double(*d)),
352        Value::Char(c) => Ok(SerializedValue::Char(*c)),
353        Value::Uuid(u) => Ok(SerializedValue::Uuid(*u)),
354
355        Value::BigInt(p) => Ok(SerializedValue::BigInt(p.get().clone())),
356        Value::BigDecimal(p) => Ok(SerializedValue::BigDecimal(p.get().clone())),
357        Value::Ratio(p) => Ok(SerializedValue::Ratio(p.get().clone())),
358        Value::Str(p) => Ok(SerializedValue::Str(p.get().clone())),
359        Value::Pattern(p) => Ok(SerializedValue::Pattern(p.get().as_str().to_owned())),
360
361        // ── Identifiers ──
362        Value::Symbol(p) => {
363            let s = p.get();
364            Ok(SerializedValue::Symbol {
365                namespace: s.namespace.clone(),
366                name: s.name.clone(),
367                version: s.version.clone(),
368            })
369        }
370        Value::Keyword(p) => {
371            let k = p.get();
372            Ok(SerializedValue::Keyword {
373                namespace: k.namespace.clone(),
374                name: k.name.clone(),
375            })
376        }
377
378        // ── Collections ──
379        Value::List(p) => {
380            let items: Result<Vec<_>, _> = p.get().iter().map(serialize).collect();
381            Ok(SerializedValue::List(items?))
382        }
383        Value::Vector(p) => {
384            let items: Result<Vec<_>, _> = p.get().iter().map(serialize).collect();
385            Ok(SerializedValue::Vector(items?))
386        }
387        Value::Map(m) => serialize_map(m),
388        Value::Set(s) => serialize_set(s),
389        Value::Queue(p) => {
390            let items: Result<Vec<_>, _> = p.get().iter().map(serialize).collect();
391            Ok(SerializedValue::Queue(items?))
392        }
393        Value::Cons(p) => {
394            let c = p.get();
395            Ok(SerializedValue::Cons {
396                head: Box::new(serialize(&c.head)?),
397                tail: Box::new(serialize(&c.tail)?),
398            })
399        }
400
401        // ── Records ──
402        Value::TypeInstance(p) => {
403            let ti = p.get();
404            let fields = serialize_map_pairs(&ti.fields)?;
405            Ok(SerializedValue::TypeInstance {
406                type_tag: ti.type_tag.clone(),
407                fields,
408            })
409        }
410
411        // ── Errors ──
412        Value::Error(p) => Ok(SerializedValue::Error(Box::new(serialize_error(p.get())?))),
413
414        // ── Primitive arrays (snapshot) ──
415        Value::BooleanArray(p) => Ok(SerializedValue::BooleanArray(
416            p.get().lock().unwrap().clone(),
417        )),
418        Value::ByteArray(p) => Ok(SerializedValue::ByteArray(p.get().lock().unwrap().clone())),
419        Value::ShortArray(p) => Ok(SerializedValue::ShortArray(p.get().lock().unwrap().clone())),
420        Value::IntArray(p) => Ok(SerializedValue::IntArray(p.get().lock().unwrap().clone())),
421        Value::LongArray(p) => Ok(SerializedValue::LongArray(p.get().lock().unwrap().clone())),
422        Value::FloatArray(p) => Ok(SerializedValue::FloatArray(p.get().lock().unwrap().clone())),
423        Value::DoubleArray(p) => Ok(SerializedValue::DoubleArray(
424            p.get().lock().unwrap().clone(),
425        )),
426        Value::CharArray(p) => Ok(SerializedValue::CharArray(p.get().lock().unwrap().clone())),
427        Value::ObjectArray(p) => {
428            let guard = p.get().0.lock().unwrap();
429            let items: Result<Vec<_>, _> = guard.iter().map(serialize).collect();
430            Ok(SerializedValue::ObjectArray(items?))
431        }
432
433        // ── Lazy sequences: realize first ──
434        Value::LazySeq(p) => serialize(&p.get().realize()),
435
436        // ── Delay: force if already realized, else error ──
437        Value::Delay(p) => {
438            let state = p.get().state.lock().unwrap();
439            if let DelayState::Forced(v) = &*state {
440                serialize(v)
441            } else {
442                Err(not_shareable("delay"))
443            }
444        }
445
446        // ── Non-shareable ──
447        Value::Resource(_) => Err(not_shareable("resource")),
448        Value::NativeObject(_) => Err(not_shareable("native-object")),
449        Value::Matcher(_) => Err(not_shareable("matcher")),
450
451        Value::Fn(_) | Value::Macro(_) => Err(not_shareable("fn")),
452        Value::BoundFn(_) => Err(not_shareable("fn")),
453        Value::NativeFunction(_) => Err(not_shareable("fn")),
454        Value::ProtocolFn(_) => Err(not_shareable("fn")),
455        Value::MultiFn(_) => Err(not_shareable("fn")),
456
457        // ── Phase B3: vars cross by sharing their root cell ──
458        Value::Var(p) => {
459            let var = p.get();
460            // The shared cell is kept in sync by `Var::bind` (promote-on-def).
461            // It is empty when the var is unbound *or* its current root is not
462            // promotable.  Distinguish the two: an unbound var may cross (it
463            // arrives unbound), but a var bound to a non-promotable value
464            // (closure / native resource) is explicitly isolate-local and is
465            // rejected here — a non-silent boundary error, not a silent drop.
466            let shared_empty = var.shared_root.load().is_none();
467            if shared_empty && var.is_bound() {
468                return Err(not_shareable("var"));
469            }
470            Ok(SerializedValue::Var {
471                namespace: var.namespace.clone(),
472                name: var.name.clone(),
473                is_macro: var.is_macro,
474                shared_root: var.shared_root.clone(),
475            })
476        }
477        Value::Atom(_) => Err(not_shareable("atom")),
478        Value::Volatile(_) => Err(not_shareable("volatile")),
479        Value::Promise(_) => Err(not_shareable("promise")),
480        Value::Future(_) => Err(not_shareable("future")),
481        Value::Agent(_) => Err(not_shareable("agent")),
482
483        Value::Namespace(_) => Err(not_shareable("namespace")),
484        Value::Protocol(_) => Err(not_shareable("protocol")),
485
486        Value::TransientMap(_) => Err(not_shareable("transient-map")),
487        Value::TransientSet(_) => Err(not_shareable("transient-set")),
488        Value::TransientVector(_) => Err(not_shareable("transient-vector")),
489
490        // ── Phase B3: cross-isolate shared references (pass Arc through) ──
491        Value::SharedAtom(a) => Ok(SerializedValue::SharedAtom(a.clone())),
492        Value::ByteBlob(b) => Ok(SerializedValue::ByteBlob(b.clone())),
493    }
494}
495
496fn serialize_map(m: &MapValue) -> Result<SerializedValue, CloneError> {
497    let pairs = serialize_map_pairs(m)?;
498    Ok(match m {
499        MapValue::Array(_) => SerializedValue::ArrayMap(pairs),
500        MapValue::Hash(_) => SerializedValue::HashMap(pairs),
501        MapValue::Sorted(_) => SerializedValue::SortedMap(pairs),
502    })
503}
504
505fn serialize_map_pairs(
506    m: &MapValue,
507) -> Result<Vec<(SerializedValue, SerializedValue)>, CloneError> {
508    let mut pairs = Vec::with_capacity(m.count());
509    let mut err: Option<CloneError> = None;
510    m.for_each(|k, v| {
511        if err.is_some() {
512            return;
513        }
514        match (serialize(k), serialize(v)) {
515            (Ok(sk), Ok(sv)) => pairs.push((sk, sv)),
516            (Err(e), _) | (_, Err(e)) => err = Some(e),
517        }
518    });
519    if let Some(e) = err { Err(e) } else { Ok(pairs) }
520}
521
522fn serialize_set(s: &SetValue) -> Result<SerializedValue, CloneError> {
523    let items: Result<Vec<_>, _> = s.iter().map(serialize).collect();
524    Ok(match s {
525        SetValue::Hash(_) => SerializedValue::HashSet(items?),
526        SetValue::Sorted(_) => SerializedValue::SortedSet(items?),
527    })
528}
529
530fn serialize_error(e: &crate::error::ExceptionInfo) -> Result<SerializedError, CloneError> {
531    let kind = match &e.error {
532        ValueError::WrongType { expected, got } => SerializedErrorKind::WrongType {
533            expected,
534            got: got.clone(),
535        },
536        ValueError::IndexOutOfBounds { idx, count } => SerializedErrorKind::IndexOutOfBounds {
537            idx: *idx,
538            count: *count,
539        },
540        ValueError::ArityError {
541            name,
542            expected,
543            got,
544        } => SerializedErrorKind::ArityError {
545            name: name.clone(),
546            expected: expected.clone(),
547            got: *got,
548        },
549        ValueError::NotCallable { value } => SerializedErrorKind::NotCallable {
550            value: value.clone(),
551        },
552        ValueError::OddMap { count } => SerializedErrorKind::OddMap { count: *count },
553        ValueError::Unsupported => SerializedErrorKind::Unsupported,
554        ValueError::Other(s) => SerializedErrorKind::Other(s.clone()),
555        ValueError::GasExhausted => SerializedErrorKind::GasExhausted,
556        ValueError::OutOfRange => SerializedErrorKind::OutOfRange,
557        ValueError::TransientAlreadyPersisted => SerializedErrorKind::TransientAlreadyPersisted,
558        ValueError::Parse => SerializedErrorKind::Parse,
559        ValueError::Thrown(v) => SerializedErrorKind::Thrown(Box::new(serialize(v)?)),
560    };
561
562    let data = e.data.as_ref().map(serialize_map_pairs).transpose()?;
563
564    let cause = e
565        .cause
566        .as_ref()
567        .map(|c| serialize_error(c.get()).map(Box::new))
568        .transpose()?;
569
570    Ok(SerializedError {
571        kind,
572        message: e.message.clone(),
573        data,
574        cause,
575    })
576}
577
578// ── deserialize ───────────────────────────────────────────────────────────────
579
580/// Deserialize a wire form into a fresh `Value` allocated in the *current*
581/// isolate's GC heap. Infallible: all non-shareable values are rejected at
582/// `serialize` time, so nothing in `SerializedValue` requires runtime checks.
583pub fn deserialize(sv: SerializedValue) -> Value {
584    match sv {
585        SerializedValue::WithMeta { value, meta } => {
586            Value::WithMeta(Box::new(deserialize(*value)), Box::new(deserialize(*meta)))
587        }
588        SerializedValue::Reduced(inner) => Value::Reduced(Box::new(deserialize(*inner))),
589
590        SerializedValue::Nil => Value::Nil,
591        SerializedValue::Bool(b) => Value::Bool(b),
592        SerializedValue::Long(n) => Value::Long(n),
593        SerializedValue::Double(d) => Value::Double(d),
594        SerializedValue::Char(c) => Value::Char(c),
595        SerializedValue::Uuid(u) => Value::Uuid(u),
596
597        SerializedValue::BigInt(n) => Value::BigInt(GcPtr::new(n)),
598        SerializedValue::BigDecimal(d) => Value::BigDecimal(GcPtr::new(d)),
599        SerializedValue::Ratio(r) => Value::Ratio(GcPtr::new(r)),
600        SerializedValue::Str(s) => Value::Str(GcPtr::new(s)),
601        SerializedValue::Pattern(src) => Value::Pattern(GcPtr::new(
602            crate::regex::Pattern::new(&src).expect("pattern was valid at serialize time"),
603        )),
604
605        SerializedValue::Symbol {
606            namespace,
607            name,
608            version,
609        } => Value::Symbol(GcPtr::new(Symbol {
610            namespace,
611            name,
612            version,
613        })),
614        SerializedValue::Keyword { namespace, name } => {
615            Value::Keyword(GcPtr::new(Keyword { namespace, name }))
616        }
617
618        SerializedValue::List(items) => Value::List(GcPtr::new(PersistentList::from_iter(
619            items.into_iter().map(deserialize),
620        ))),
621        SerializedValue::Vector(items) => Value::Vector(GcPtr::new(PersistentVector::from_iter(
622            items.into_iter().map(deserialize),
623        ))),
624        SerializedValue::ArrayMap(pairs) => Value::Map(MapValue::from_pairs(
625            pairs
626                .into_iter()
627                .map(|(k, v)| (deserialize(k), deserialize(v)))
628                .collect(),
629        )),
630        SerializedValue::HashMap(pairs) => Value::Map(MapValue::from_pairs(
631            pairs
632                .into_iter()
633                .map(|(k, v)| (deserialize(k), deserialize(v)))
634                .collect(),
635        )),
636        SerializedValue::SortedMap(pairs) => {
637            // Rebuild as a sorted map through the standard sorted-map path.
638            let items: Vec<(Value, Value)> = pairs
639                .into_iter()
640                .map(|(k, v)| (deserialize(k), deserialize(v)))
641                .collect();
642            let sm = SortedMap::from_pairs(items);
643            Value::Map(MapValue::Sorted(GcPtr::new(sm)))
644        }
645        SerializedValue::HashSet(items) => {
646            let mut hs = PersistentHashSet::empty();
647            for item in items.into_iter().map(deserialize) {
648                hs = hs.conj(item);
649            }
650            Value::Set(SetValue::Hash(GcPtr::new(hs)))
651        }
652        SerializedValue::SortedSet(items) => {
653            let mut ss = SortedSet::empty();
654            for item in items.into_iter().map(deserialize) {
655                ss = ss.conj(item);
656            }
657            Value::Set(SetValue::Sorted(GcPtr::new(ss)))
658        }
659        SerializedValue::Queue(items) => {
660            let mut q = PersistentQueue::empty();
661            for item in items.into_iter().map(deserialize) {
662                q = q.conj(item);
663            }
664            Value::Queue(GcPtr::new(q))
665        }
666        SerializedValue::Cons { head, tail } => {
667            use crate::types::CljxCons;
668            Value::Cons(GcPtr::new(CljxCons {
669                head: deserialize(*head),
670                tail: deserialize(*tail),
671            }))
672        }
673
674        SerializedValue::TypeInstance { type_tag, fields } => {
675            use crate::value::TypeInstance;
676            let pairs: Vec<(Value, Value)> = fields
677                .into_iter()
678                .map(|(k, v)| (deserialize(k), deserialize(v)))
679                .collect();
680            Value::TypeInstance(GcPtr::new(TypeInstance {
681                type_tag,
682                fields: MapValue::from_pairs(pairs),
683            }))
684        }
685
686        SerializedValue::Error(se) => Value::Error(GcPtr::new(deserialize_error(*se))),
687
688        SerializedValue::BooleanArray(v) => {
689            Value::BooleanArray(GcPtr::new(std::sync::Mutex::new(v)))
690        }
691        SerializedValue::ByteArray(v) => Value::ByteArray(GcPtr::new(std::sync::Mutex::new(v))),
692        SerializedValue::ShortArray(v) => Value::ShortArray(GcPtr::new(std::sync::Mutex::new(v))),
693        SerializedValue::IntArray(v) => Value::IntArray(GcPtr::new(std::sync::Mutex::new(v))),
694        SerializedValue::LongArray(v) => Value::LongArray(GcPtr::new(std::sync::Mutex::new(v))),
695        SerializedValue::FloatArray(v) => Value::FloatArray(GcPtr::new(std::sync::Mutex::new(v))),
696        SerializedValue::DoubleArray(v) => Value::DoubleArray(GcPtr::new(std::sync::Mutex::new(v))),
697        SerializedValue::CharArray(v) => Value::CharArray(GcPtr::new(std::sync::Mutex::new(v))),
698        SerializedValue::ObjectArray(items) => {
699            use crate::value::ObjectArray;
700            Value::ObjectArray(GcPtr::new(ObjectArray::new(
701                items.into_iter().map(deserialize).collect(),
702            )))
703        }
704
705        // Phase B3: cross-isolate shared references — clone the Arc.
706        SerializedValue::SharedAtom(a) => Value::SharedAtom(a),
707        SerializedValue::ByteBlob(b) => Value::ByteBlob(b),
708        SerializedValue::Var {
709            namespace,
710            name,
711            is_macro,
712            shared_root,
713        } => Value::Var(GcPtr::new(crate::types::Var::from_shared_root(
714            namespace,
715            name,
716            is_macro,
717            shared_root,
718        ))),
719    }
720}
721
722fn deserialize_error(se: SerializedError) -> crate::error::ExceptionInfo {
723    let error = match se.kind {
724        SerializedErrorKind::WrongType { expected, got } => ValueError::WrongType { expected, got },
725        SerializedErrorKind::IndexOutOfBounds { idx, count } => {
726            ValueError::IndexOutOfBounds { idx, count }
727        }
728        SerializedErrorKind::ArityError {
729            name,
730            expected,
731            got,
732        } => ValueError::ArityError {
733            name,
734            expected,
735            got,
736        },
737        SerializedErrorKind::NotCallable { value } => ValueError::NotCallable { value },
738        SerializedErrorKind::OddMap { count } => ValueError::OddMap { count },
739        SerializedErrorKind::Unsupported => ValueError::Unsupported,
740        SerializedErrorKind::Other(s) => ValueError::Other(s),
741        SerializedErrorKind::GasExhausted => ValueError::GasExhausted,
742        SerializedErrorKind::OutOfRange => ValueError::OutOfRange,
743        SerializedErrorKind::TransientAlreadyPersisted => ValueError::TransientAlreadyPersisted,
744        SerializedErrorKind::Parse => ValueError::Parse,
745        SerializedErrorKind::Thrown(sv) => ValueError::Thrown(deserialize(*sv)),
746    };
747
748    let data = se.data.map(|pairs| {
749        MapValue::from_pairs(
750            pairs
751                .into_iter()
752                .map(|(k, v)| (deserialize(k), deserialize(v)))
753                .collect(),
754        )
755    });
756
757    let cause = se.cause.map(|c| GcPtr::new(deserialize_error(*c)));
758
759    crate::error::ExceptionInfo::new(error, se.message, data, cause)
760}
761
762// ── Tests ─────────────────────────────────────────────────────────────────────
763
764#[cfg(test)]
765mod tests {
766    use super::*;
767
768    fn roundtrip(v: &Value) -> Value {
769        deserialize(serialize(v).expect("serialize"))
770    }
771
772    #[test]
773    fn scalars_roundtrip() {
774        assert_eq!(roundtrip(&Value::Nil), Value::Nil);
775        assert_eq!(roundtrip(&Value::Bool(true)), Value::Bool(true));
776        assert_eq!(roundtrip(&Value::Long(42)), Value::Long(42));
777        assert_eq!(roundtrip(&Value::Char('x')), Value::Char('x'));
778        assert_eq!(roundtrip(&Value::Uuid(12345)), Value::Uuid(12345));
779    }
780
781    #[test]
782    fn string_roundtrip() {
783        let v = Value::string("hello, world");
784        assert_eq!(roundtrip(&v), v);
785    }
786
787    #[test]
788    fn keyword_roundtrip() {
789        let v = Value::keyword(Keyword::simple("foo"));
790        assert_eq!(roundtrip(&v), v);
791        let v2 = Value::keyword(Keyword::qualified("clojure.core", "map"));
792        assert_eq!(roundtrip(&v2), v2);
793    }
794
795    #[test]
796    fn symbol_roundtrip() {
797        let v = Value::symbol(Symbol::simple("my-fn"));
798        assert_eq!(roundtrip(&v), v);
799    }
800
801    #[test]
802    fn list_roundtrip() {
803        let v = Value::List(GcPtr::new(PersistentList::from_iter([
804            Value::Long(1),
805            Value::Long(2),
806            Value::Long(3),
807        ])));
808        assert_eq!(roundtrip(&v), v);
809    }
810
811    #[test]
812    fn vector_roundtrip() {
813        let v = Value::Vector(GcPtr::new(PersistentVector::from_iter([
814            Value::string("a"),
815            Value::Bool(false),
816            Value::Nil,
817        ])));
818        assert_eq!(roundtrip(&v), v);
819    }
820
821    #[test]
822    fn nested_map_roundtrip() {
823        let inner = Value::Vector(GcPtr::new(PersistentVector::from_iter([Value::Long(1)])));
824        let v = MapValue::from_pairs(vec![(Value::keyword(Keyword::simple("k")), inner)]);
825        let v = Value::Map(v);
826        assert_eq!(roundtrip(&v), v);
827    }
828
829    #[test]
830    fn resource_not_shareable() {
831        use crate::resource::ResourceHandle;
832        use std::any::Any;
833        use std::sync::Arc;
834        #[derive(Debug)]
835        struct FakeResource;
836        impl crate::resource::Resource for FakeResource {
837            fn resource_type(&self) -> &'static str {
838                "fake"
839            }
840            fn close(&self) -> crate::error::ValueResult<()> {
841                Ok(())
842            }
843            fn is_closed(&self) -> bool {
844                false
845            }
846            fn as_any(&self) -> &dyn Any {
847                self
848            }
849        }
850        let r = Value::Resource(ResourceHandle(Arc::new(FakeResource)));
851        assert!(matches!(
852            serialize(&r),
853            Err(CloneError::NotShareable {
854                type_name: "resource"
855            })
856        ));
857    }
858
859    #[test]
860    fn atom_not_shareable() {
861        use crate::types::Atom;
862        let a = Value::Atom(GcPtr::new(Atom::new(Value::Nil)));
863        assert!(matches!(
864            serialize(&a),
865            Err(CloneError::NotShareable { type_name: "atom" })
866        ));
867    }
868
869    #[test]
870    fn fn_not_shareable() {
871        use crate::types::{Arity, NativeFn};
872        let nf = Value::NativeFunction(GcPtr::new(NativeFn::new("test", Arity::Fixed(0), |_| {
873            Ok(Value::Nil)
874        })));
875        assert!(matches!(
876            serialize(&nf),
877            Err(CloneError::NotShareable { type_name: "fn" })
878        ));
879    }
880
881    #[test]
882    fn lazy_seq_realized_roundtrip() {
883        use crate::types::{LazySeq, Thunk};
884        struct DoneThunk;
885        impl std::fmt::Debug for DoneThunk {
886            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
887                write!(f, "DoneThunk")
888            }
889        }
890        impl cljrs_gc::Trace for DoneThunk {
891            fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
892        }
893        impl Thunk for DoneThunk {
894            fn force(&self) -> Result<Value, String> {
895                Ok(Value::Long(99))
896            }
897        }
898        let ls = LazySeq::new(Box::new(DoneThunk));
899        let _ = ls.realize(); // force it
900        let v = Value::LazySeq(GcPtr::new(ls));
901        assert_eq!(roundtrip(&v), Value::Long(99));
902    }
903
904    #[test]
905    fn with_meta_roundtrip() {
906        let v = Value::Long(7).with_meta(Value::Map(MapValue::empty()));
907        let rt = roundtrip(&v);
908        // WithMeta strips for equality, so unwrap and check inner
909        assert_eq!(rt, Value::Long(7));
910    }
911
912    #[test]
913    fn reduced_roundtrip() {
914        let v = Value::Reduced(Box::new(Value::Long(55)));
915        assert_eq!(roundtrip(&v), Value::Reduced(Box::new(Value::Long(55))));
916    }
917
918    #[test]
919    fn byte_size_counts_string_payload() {
920        let small = serialize(&Value::string("hi")).unwrap();
921        let large = serialize(&Value::string("x".repeat(1000))).unwrap();
922        // Same variant, so the difference is the string payload (~998 bytes).
923        assert!(large.byte_size() > small.byte_size() + 900);
924    }
925
926    #[test]
927    fn byte_size_grows_with_collection() {
928        let small = serialize(&Value::Vector(GcPtr::new(PersistentVector::from_iter([
929            Value::Long(1),
930        ]))))
931        .unwrap();
932        let large = serialize(&Value::Vector(GcPtr::new(PersistentVector::from_iter(
933            (0..100).map(Value::Long),
934        ))))
935        .unwrap();
936        assert!(large.byte_size() > small.byte_size());
937    }
938
939    #[test]
940    fn var_with_promotable_root_roundtrips() {
941        use crate::types::Var;
942        // A var def'd with a promotable value crosses by value.
943        let var = Var::new("user", "answer");
944        var.bind(Value::Long(42));
945        let v = Value::Var(GcPtr::new(var));
946
947        let crossed = roundtrip(&v);
948        if let Value::Var(p) = crossed {
949            let got = p.get();
950            assert_eq!(got.namespace.as_ref(), "user");
951            assert_eq!(got.name.as_ref(), "answer");
952            // Observable by value on the receiving side.
953            assert_eq!(got.deref(), Some(Value::Long(42)));
954        } else {
955            panic!("expected a Var on the receiving side");
956        }
957    }
958
959    #[test]
960    fn var_keyword_root_preserves_identity() {
961        use crate::types::Var;
962        let var = Var::new("user", "k");
963        var.bind(Value::keyword(Keyword::qualified("ns", "kw")));
964        let v = Value::Var(GcPtr::new(var));
965
966        let crossed = roundtrip(&v);
967        let Value::Var(p) = crossed else {
968            panic!("expected Var")
969        };
970        // Keyword identity preserved through the intern table on demote.
971        assert_eq!(
972            p.get().deref(),
973            Some(Value::keyword(Keyword::qualified("ns", "kw")))
974        );
975    }
976
977    #[test]
978    fn var_shares_root_cell_across_boundary() {
979        use crate::types::Var;
980        // Both isolates point at the *same* shared root cell, so a write on the
981        // sending side is observable through the receiver's shared view.
982        let var = GcPtr::new(Var::new("user", "shared"));
983        var.get().bind(Value::Long(1));
984        let v = Value::Var(var.clone());
985
986        let Value::Var(recv) = roundtrip(&v) else {
987            panic!("expected Var")
988        };
989        assert_eq!(recv.get().deref_shared(), Some(Value::Long(1)));
990
991        // Sender re-defs through the same cell; receiver observes via the cell.
992        var.get().bind(Value::Long(2));
993        assert_eq!(recv.get().deref_shared(), Some(Value::Long(2)));
994    }
995
996    #[test]
997    fn var_with_nonpromotable_root_is_not_shareable() {
998        use crate::types::{Arity, NativeFn, Var};
999        // A var bound to a closure / native fn is explicitly isolate-local.
1000        let var = Var::new("user", "f");
1001        var.bind(Value::NativeFunction(GcPtr::new(NativeFn::new(
1002            "f",
1003            Arity::Fixed(0),
1004            |_| Ok(Value::Nil),
1005        ))));
1006        let v = Value::Var(GcPtr::new(var));
1007        assert!(matches!(
1008            serialize(&v),
1009            Err(CloneError::NotShareable { type_name: "var" })
1010        ));
1011    }
1012
1013    #[test]
1014    fn unbound_var_crosses_as_unbound() {
1015        use crate::types::Var;
1016        let v = Value::Var(GcPtr::new(Var::new("user", "later")));
1017        let Value::Var(p) = roundtrip(&v) else {
1018            panic!("expected Var")
1019        };
1020        assert!(!p.get().is_bound());
1021    }
1022
1023    #[test]
1024    fn byte_size_scalar_is_node_sized() {
1025        // A scalar has no owned payload, so it costs exactly one node.
1026        let node = std::mem::size_of::<SerializedValue>();
1027        assert_eq!(serialize(&Value::Long(7)).unwrap().byte_size(), node);
1028        assert_eq!(serialize(&Value::Nil).unwrap().byte_size(), node);
1029    }
1030}