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