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