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
170/// Serialized form of [`crate::error::ExceptionInfo`].
171#[derive(Clone, Debug)]
172pub struct SerializedError {
173    pub kind: SerializedErrorKind,
174    pub message: String,
175    pub data: Option<Vec<(SerializedValue, SerializedValue)>>,
176    pub cause: Option<Box<SerializedError>>,
177}
178
179/// Mirrors [`ValueError`] with `Value` replaced by `SerializedValue`.
180#[derive(Clone, Debug)]
181pub enum SerializedErrorKind {
182    WrongType {
183        expected: &'static str,
184        got: String,
185    },
186    IndexOutOfBounds {
187        idx: usize,
188        count: usize,
189    },
190    ArityError {
191        name: String,
192        expected: String,
193        got: usize,
194    },
195    NotCallable {
196        value: String,
197    },
198    OddMap {
199        count: usize,
200    },
201    Unsupported,
202    Other(String),
203    OutOfRange,
204    TransientAlreadyPersisted,
205    Parse,
206    Thrown(Box<SerializedValue>),
207}
208
209// ── serialize ─────────────────────────────────────────────────────────────────
210
211/// Serialize a `Value` into a `Send + Sync` wire form suitable for crossing an
212/// isolate boundary. Returns [`CloneError`] for non-shareable values.
213pub fn serialize(v: &Value) -> Result<SerializedValue, CloneError> {
214    match v {
215        // ── Wrappers ──
216        Value::WithMeta(inner, meta) => Ok(SerializedValue::WithMeta {
217            value: Box::new(serialize(inner)?),
218            meta: Box::new(serialize(meta)?),
219        }),
220        Value::Reduced(inner) => Ok(SerializedValue::Reduced(Box::new(serialize(inner)?))),
221
222        // ── Scalars ──
223        Value::Nil => Ok(SerializedValue::Nil),
224        Value::Bool(b) => Ok(SerializedValue::Bool(*b)),
225        Value::Long(n) => Ok(SerializedValue::Long(*n)),
226        Value::Double(d) => Ok(SerializedValue::Double(*d)),
227        Value::Char(c) => Ok(SerializedValue::Char(*c)),
228        Value::Uuid(u) => Ok(SerializedValue::Uuid(*u)),
229
230        Value::BigInt(p) => Ok(SerializedValue::BigInt(p.get().clone())),
231        Value::BigDecimal(p) => Ok(SerializedValue::BigDecimal(p.get().clone())),
232        Value::Ratio(p) => Ok(SerializedValue::Ratio(p.get().clone())),
233        Value::Str(p) => Ok(SerializedValue::Str(p.get().clone())),
234        Value::Pattern(p) => Ok(SerializedValue::Pattern(p.get().as_str().to_owned())),
235
236        // ── Identifiers ──
237        Value::Symbol(p) => {
238            let s = p.get();
239            Ok(SerializedValue::Symbol {
240                namespace: s.namespace.clone(),
241                name: s.name.clone(),
242                version: s.version.clone(),
243            })
244        }
245        Value::Keyword(p) => {
246            let k = p.get();
247            Ok(SerializedValue::Keyword {
248                namespace: k.namespace.clone(),
249                name: k.name.clone(),
250            })
251        }
252
253        // ── Collections ──
254        Value::List(p) => {
255            let items: Result<Vec<_>, _> = p.get().iter().map(serialize).collect();
256            Ok(SerializedValue::List(items?))
257        }
258        Value::Vector(p) => {
259            let items: Result<Vec<_>, _> = p.get().iter().map(serialize).collect();
260            Ok(SerializedValue::Vector(items?))
261        }
262        Value::Map(m) => serialize_map(m),
263        Value::Set(s) => serialize_set(s),
264        Value::Queue(p) => {
265            let items: Result<Vec<_>, _> = p.get().iter().map(serialize).collect();
266            Ok(SerializedValue::Queue(items?))
267        }
268        Value::Cons(p) => {
269            let c = p.get();
270            Ok(SerializedValue::Cons {
271                head: Box::new(serialize(&c.head)?),
272                tail: Box::new(serialize(&c.tail)?),
273            })
274        }
275
276        // ── Records ──
277        Value::TypeInstance(p) => {
278            let ti = p.get();
279            let fields = serialize_map_pairs(&ti.fields)?;
280            Ok(SerializedValue::TypeInstance {
281                type_tag: ti.type_tag.clone(),
282                fields,
283            })
284        }
285
286        // ── Errors ──
287        Value::Error(p) => Ok(SerializedValue::Error(Box::new(serialize_error(p.get())?))),
288
289        // ── Primitive arrays (snapshot) ──
290        Value::BooleanArray(p) => Ok(SerializedValue::BooleanArray(
291            p.get().lock().unwrap().clone(),
292        )),
293        Value::ByteArray(p) => Ok(SerializedValue::ByteArray(p.get().lock().unwrap().clone())),
294        Value::ShortArray(p) => Ok(SerializedValue::ShortArray(p.get().lock().unwrap().clone())),
295        Value::IntArray(p) => Ok(SerializedValue::IntArray(p.get().lock().unwrap().clone())),
296        Value::LongArray(p) => Ok(SerializedValue::LongArray(p.get().lock().unwrap().clone())),
297        Value::FloatArray(p) => Ok(SerializedValue::FloatArray(p.get().lock().unwrap().clone())),
298        Value::DoubleArray(p) => Ok(SerializedValue::DoubleArray(
299            p.get().lock().unwrap().clone(),
300        )),
301        Value::CharArray(p) => Ok(SerializedValue::CharArray(p.get().lock().unwrap().clone())),
302        Value::ObjectArray(p) => {
303            let guard = p.get().0.lock().unwrap();
304            let items: Result<Vec<_>, _> = guard.iter().map(serialize).collect();
305            Ok(SerializedValue::ObjectArray(items?))
306        }
307
308        // ── Lazy sequences: realize first ──
309        Value::LazySeq(p) => serialize(&p.get().realize()),
310
311        // ── Delay: force if already realized, else error ──
312        Value::Delay(p) => {
313            let state = p.get().state.lock().unwrap();
314            if let DelayState::Forced(v) = &*state {
315                serialize(v)
316            } else {
317                Err(not_shareable("delay"))
318            }
319        }
320
321        // ── Non-shareable ──
322        Value::Resource(_) => Err(not_shareable("resource")),
323        Value::NativeObject(_) => Err(not_shareable("native-object")),
324        Value::Matcher(_) => Err(not_shareable("matcher")),
325
326        Value::Fn(_) | Value::Macro(_) => Err(not_shareable("fn")),
327        Value::BoundFn(_) => Err(not_shareable("fn")),
328        Value::NativeFunction(_) => Err(not_shareable("fn")),
329        Value::ProtocolFn(_) => Err(not_shareable("fn")),
330        Value::MultiFn(_) => Err(not_shareable("fn")),
331
332        Value::Var(_) => Err(not_shareable("var")),
333        Value::Atom(_) => Err(not_shareable("atom")),
334        Value::Volatile(_) => Err(not_shareable("volatile")),
335        Value::Promise(_) => Err(not_shareable("promise")),
336        Value::Future(_) => Err(not_shareable("future")),
337        Value::Agent(_) => Err(not_shareable("agent")),
338
339        Value::Namespace(_) => Err(not_shareable("namespace")),
340        Value::Protocol(_) => Err(not_shareable("protocol")),
341
342        Value::TransientMap(_) => Err(not_shareable("transient-map")),
343        Value::TransientSet(_) => Err(not_shareable("transient-set")),
344        Value::TransientVector(_) => Err(not_shareable("transient-vector")),
345
346        // ── Phase B3: cross-isolate shared references (pass Arc through) ──
347        Value::SharedAtom(a) => Ok(SerializedValue::SharedAtom(a.clone())),
348        Value::ByteBlob(b) => Ok(SerializedValue::ByteBlob(b.clone())),
349    }
350}
351
352fn serialize_map(m: &MapValue) -> Result<SerializedValue, CloneError> {
353    let pairs = serialize_map_pairs(m)?;
354    Ok(match m {
355        MapValue::Array(_) => SerializedValue::ArrayMap(pairs),
356        MapValue::Hash(_) => SerializedValue::HashMap(pairs),
357        MapValue::Sorted(_) => SerializedValue::SortedMap(pairs),
358    })
359}
360
361fn serialize_map_pairs(
362    m: &MapValue,
363) -> Result<Vec<(SerializedValue, SerializedValue)>, CloneError> {
364    let mut pairs = Vec::with_capacity(m.count());
365    let mut err: Option<CloneError> = None;
366    m.for_each(|k, v| {
367        if err.is_some() {
368            return;
369        }
370        match (serialize(k), serialize(v)) {
371            (Ok(sk), Ok(sv)) => pairs.push((sk, sv)),
372            (Err(e), _) | (_, Err(e)) => err = Some(e),
373        }
374    });
375    if let Some(e) = err { Err(e) } else { Ok(pairs) }
376}
377
378fn serialize_set(s: &SetValue) -> Result<SerializedValue, CloneError> {
379    let items: Result<Vec<_>, _> = s.iter().map(serialize).collect();
380    Ok(match s {
381        SetValue::Hash(_) => SerializedValue::HashSet(items?),
382        SetValue::Sorted(_) => SerializedValue::SortedSet(items?),
383    })
384}
385
386fn serialize_error(e: &crate::error::ExceptionInfo) -> Result<SerializedError, CloneError> {
387    let kind = match &e.error {
388        ValueError::WrongType { expected, got } => SerializedErrorKind::WrongType {
389            expected,
390            got: got.clone(),
391        },
392        ValueError::IndexOutOfBounds { idx, count } => SerializedErrorKind::IndexOutOfBounds {
393            idx: *idx,
394            count: *count,
395        },
396        ValueError::ArityError {
397            name,
398            expected,
399            got,
400        } => SerializedErrorKind::ArityError {
401            name: name.clone(),
402            expected: expected.clone(),
403            got: *got,
404        },
405        ValueError::NotCallable { value } => SerializedErrorKind::NotCallable {
406            value: value.clone(),
407        },
408        ValueError::OddMap { count } => SerializedErrorKind::OddMap { count: *count },
409        ValueError::Unsupported => SerializedErrorKind::Unsupported,
410        ValueError::Other(s) => SerializedErrorKind::Other(s.clone()),
411        ValueError::OutOfRange => SerializedErrorKind::OutOfRange,
412        ValueError::TransientAlreadyPersisted => SerializedErrorKind::TransientAlreadyPersisted,
413        ValueError::Parse => SerializedErrorKind::Parse,
414        ValueError::Thrown(v) => SerializedErrorKind::Thrown(Box::new(serialize(v)?)),
415    };
416
417    let data = e.data.as_ref().map(serialize_map_pairs).transpose()?;
418
419    let cause = e
420        .cause
421        .as_ref()
422        .map(|c| serialize_error(c.get()).map(Box::new))
423        .transpose()?;
424
425    Ok(SerializedError {
426        kind,
427        message: e.message.clone(),
428        data,
429        cause,
430    })
431}
432
433// ── deserialize ───────────────────────────────────────────────────────────────
434
435/// Deserialize a wire form into a fresh `Value` allocated in the *current*
436/// isolate's GC heap. Infallible: all non-shareable values are rejected at
437/// `serialize` time, so nothing in `SerializedValue` requires runtime checks.
438pub fn deserialize(sv: SerializedValue) -> Value {
439    match sv {
440        SerializedValue::WithMeta { value, meta } => {
441            Value::WithMeta(Box::new(deserialize(*value)), Box::new(deserialize(*meta)))
442        }
443        SerializedValue::Reduced(inner) => Value::Reduced(Box::new(deserialize(*inner))),
444
445        SerializedValue::Nil => Value::Nil,
446        SerializedValue::Bool(b) => Value::Bool(b),
447        SerializedValue::Long(n) => Value::Long(n),
448        SerializedValue::Double(d) => Value::Double(d),
449        SerializedValue::Char(c) => Value::Char(c),
450        SerializedValue::Uuid(u) => Value::Uuid(u),
451
452        SerializedValue::BigInt(n) => Value::BigInt(GcPtr::new(n)),
453        SerializedValue::BigDecimal(d) => Value::BigDecimal(GcPtr::new(d)),
454        SerializedValue::Ratio(r) => Value::Ratio(GcPtr::new(r)),
455        SerializedValue::Str(s) => Value::Str(GcPtr::new(s)),
456        SerializedValue::Pattern(src) => Value::Pattern(GcPtr::new(
457            regex::Regex::new(&src).expect("pattern was valid at serialize time"),
458        )),
459
460        SerializedValue::Symbol {
461            namespace,
462            name,
463            version,
464        } => Value::Symbol(GcPtr::new(Symbol {
465            namespace,
466            name,
467            version,
468        })),
469        SerializedValue::Keyword { namespace, name } => {
470            Value::Keyword(GcPtr::new(Keyword { namespace, name }))
471        }
472
473        SerializedValue::List(items) => Value::List(GcPtr::new(PersistentList::from_iter(
474            items.into_iter().map(deserialize),
475        ))),
476        SerializedValue::Vector(items) => Value::Vector(GcPtr::new(PersistentVector::from_iter(
477            items.into_iter().map(deserialize),
478        ))),
479        SerializedValue::ArrayMap(pairs) => Value::Map(MapValue::from_pairs(
480            pairs
481                .into_iter()
482                .map(|(k, v)| (deserialize(k), deserialize(v)))
483                .collect(),
484        )),
485        SerializedValue::HashMap(pairs) => Value::Map(MapValue::from_pairs(
486            pairs
487                .into_iter()
488                .map(|(k, v)| (deserialize(k), deserialize(v)))
489                .collect(),
490        )),
491        SerializedValue::SortedMap(pairs) => {
492            // Rebuild as a sorted map through the standard sorted-map path.
493            let items: Vec<(Value, Value)> = pairs
494                .into_iter()
495                .map(|(k, v)| (deserialize(k), deserialize(v)))
496                .collect();
497            let sm = SortedMap::from_pairs(items);
498            Value::Map(MapValue::Sorted(GcPtr::new(sm)))
499        }
500        SerializedValue::HashSet(items) => {
501            let mut hs = PersistentHashSet::empty();
502            for item in items.into_iter().map(deserialize) {
503                hs = hs.conj(item);
504            }
505            Value::Set(SetValue::Hash(GcPtr::new(hs)))
506        }
507        SerializedValue::SortedSet(items) => {
508            let mut ss = SortedSet::empty();
509            for item in items.into_iter().map(deserialize) {
510                ss = ss.conj(item);
511            }
512            Value::Set(SetValue::Sorted(GcPtr::new(ss)))
513        }
514        SerializedValue::Queue(items) => {
515            let mut q = PersistentQueue::empty();
516            for item in items.into_iter().map(deserialize) {
517                q = q.conj(item);
518            }
519            Value::Queue(GcPtr::new(q))
520        }
521        SerializedValue::Cons { head, tail } => {
522            use crate::types::CljxCons;
523            Value::Cons(GcPtr::new(CljxCons {
524                head: deserialize(*head),
525                tail: deserialize(*tail),
526            }))
527        }
528
529        SerializedValue::TypeInstance { type_tag, fields } => {
530            use crate::value::TypeInstance;
531            let pairs: Vec<(Value, Value)> = fields
532                .into_iter()
533                .map(|(k, v)| (deserialize(k), deserialize(v)))
534                .collect();
535            Value::TypeInstance(GcPtr::new(TypeInstance {
536                type_tag,
537                fields: MapValue::from_pairs(pairs),
538            }))
539        }
540
541        SerializedValue::Error(se) => Value::Error(GcPtr::new(deserialize_error(*se))),
542
543        SerializedValue::BooleanArray(v) => {
544            Value::BooleanArray(GcPtr::new(std::sync::Mutex::new(v)))
545        }
546        SerializedValue::ByteArray(v) => Value::ByteArray(GcPtr::new(std::sync::Mutex::new(v))),
547        SerializedValue::ShortArray(v) => Value::ShortArray(GcPtr::new(std::sync::Mutex::new(v))),
548        SerializedValue::IntArray(v) => Value::IntArray(GcPtr::new(std::sync::Mutex::new(v))),
549        SerializedValue::LongArray(v) => Value::LongArray(GcPtr::new(std::sync::Mutex::new(v))),
550        SerializedValue::FloatArray(v) => Value::FloatArray(GcPtr::new(std::sync::Mutex::new(v))),
551        SerializedValue::DoubleArray(v) => Value::DoubleArray(GcPtr::new(std::sync::Mutex::new(v))),
552        SerializedValue::CharArray(v) => Value::CharArray(GcPtr::new(std::sync::Mutex::new(v))),
553        SerializedValue::ObjectArray(items) => {
554            use crate::value::ObjectArray;
555            Value::ObjectArray(GcPtr::new(ObjectArray::new(
556                items.into_iter().map(deserialize).collect(),
557            )))
558        }
559
560        // Phase B3: cross-isolate shared references — clone the Arc.
561        SerializedValue::SharedAtom(a) => Value::SharedAtom(a),
562        SerializedValue::ByteBlob(b) => Value::ByteBlob(b),
563    }
564}
565
566fn deserialize_error(se: SerializedError) -> crate::error::ExceptionInfo {
567    let error = match se.kind {
568        SerializedErrorKind::WrongType { expected, got } => ValueError::WrongType { expected, got },
569        SerializedErrorKind::IndexOutOfBounds { idx, count } => {
570            ValueError::IndexOutOfBounds { idx, count }
571        }
572        SerializedErrorKind::ArityError {
573            name,
574            expected,
575            got,
576        } => ValueError::ArityError {
577            name,
578            expected,
579            got,
580        },
581        SerializedErrorKind::NotCallable { value } => ValueError::NotCallable { value },
582        SerializedErrorKind::OddMap { count } => ValueError::OddMap { count },
583        SerializedErrorKind::Unsupported => ValueError::Unsupported,
584        SerializedErrorKind::Other(s) => ValueError::Other(s),
585        SerializedErrorKind::OutOfRange => ValueError::OutOfRange,
586        SerializedErrorKind::TransientAlreadyPersisted => ValueError::TransientAlreadyPersisted,
587        SerializedErrorKind::Parse => ValueError::Parse,
588        SerializedErrorKind::Thrown(sv) => ValueError::Thrown(deserialize(*sv)),
589    };
590
591    let data = se.data.map(|pairs| {
592        MapValue::from_pairs(
593            pairs
594                .into_iter()
595                .map(|(k, v)| (deserialize(k), deserialize(v)))
596                .collect(),
597        )
598    });
599
600    let cause = se.cause.map(|c| GcPtr::new(deserialize_error(*c)));
601
602    crate::error::ExceptionInfo::new(error, se.message, data, cause)
603}
604
605// ── Tests ─────────────────────────────────────────────────────────────────────
606
607#[cfg(test)]
608mod tests {
609    use super::*;
610
611    fn roundtrip(v: &Value) -> Value {
612        deserialize(serialize(v).expect("serialize"))
613    }
614
615    #[test]
616    fn scalars_roundtrip() {
617        assert_eq!(roundtrip(&Value::Nil), Value::Nil);
618        assert_eq!(roundtrip(&Value::Bool(true)), Value::Bool(true));
619        assert_eq!(roundtrip(&Value::Long(42)), Value::Long(42));
620        assert_eq!(roundtrip(&Value::Char('x')), Value::Char('x'));
621        assert_eq!(roundtrip(&Value::Uuid(12345)), Value::Uuid(12345));
622    }
623
624    #[test]
625    fn string_roundtrip() {
626        let v = Value::string("hello, world");
627        assert_eq!(roundtrip(&v), v);
628    }
629
630    #[test]
631    fn keyword_roundtrip() {
632        let v = Value::keyword(Keyword::simple("foo"));
633        assert_eq!(roundtrip(&v), v);
634        let v2 = Value::keyword(Keyword::qualified("clojure.core", "map"));
635        assert_eq!(roundtrip(&v2), v2);
636    }
637
638    #[test]
639    fn symbol_roundtrip() {
640        let v = Value::symbol(Symbol::simple("my-fn"));
641        assert_eq!(roundtrip(&v), v);
642    }
643
644    #[test]
645    fn list_roundtrip() {
646        let v = Value::List(GcPtr::new(PersistentList::from_iter([
647            Value::Long(1),
648            Value::Long(2),
649            Value::Long(3),
650        ])));
651        assert_eq!(roundtrip(&v), v);
652    }
653
654    #[test]
655    fn vector_roundtrip() {
656        let v = Value::Vector(GcPtr::new(PersistentVector::from_iter([
657            Value::string("a"),
658            Value::Bool(false),
659            Value::Nil,
660        ])));
661        assert_eq!(roundtrip(&v), v);
662    }
663
664    #[test]
665    fn nested_map_roundtrip() {
666        let inner = Value::Vector(GcPtr::new(PersistentVector::from_iter([Value::Long(1)])));
667        let v = MapValue::from_pairs(vec![(Value::keyword(Keyword::simple("k")), inner)]);
668        let v = Value::Map(v);
669        assert_eq!(roundtrip(&v), v);
670    }
671
672    #[test]
673    fn resource_not_shareable() {
674        use crate::resource::ResourceHandle;
675        use std::any::Any;
676        use std::sync::Arc;
677        #[derive(Debug)]
678        struct FakeResource;
679        impl crate::resource::Resource for FakeResource {
680            fn resource_type(&self) -> &'static str {
681                "fake"
682            }
683            fn close(&self) -> crate::error::ValueResult<()> {
684                Ok(())
685            }
686            fn is_closed(&self) -> bool {
687                false
688            }
689            fn as_any(&self) -> &dyn Any {
690                self
691            }
692        }
693        let r = Value::Resource(ResourceHandle(Arc::new(FakeResource)));
694        assert!(matches!(
695            serialize(&r),
696            Err(CloneError::NotShareable {
697                type_name: "resource"
698            })
699        ));
700    }
701
702    #[test]
703    fn atom_not_shareable() {
704        use crate::types::Atom;
705        let a = Value::Atom(GcPtr::new(Atom::new(Value::Nil)));
706        assert!(matches!(
707            serialize(&a),
708            Err(CloneError::NotShareable { type_name: "atom" })
709        ));
710    }
711
712    #[test]
713    fn fn_not_shareable() {
714        use crate::types::{Arity, CljxFn, NativeFn};
715        let nf = Value::NativeFunction(GcPtr::new(NativeFn::new("test", Arity::Fixed(0), |_| {
716            Ok(Value::Nil)
717        })));
718        assert!(matches!(
719            serialize(&nf),
720            Err(CloneError::NotShareable { type_name: "fn" })
721        ));
722    }
723
724    #[test]
725    fn lazy_seq_realized_roundtrip() {
726        use crate::types::{LazySeq, Thunk};
727        struct DoneThunk;
728        impl std::fmt::Debug for DoneThunk {
729            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
730                write!(f, "DoneThunk")
731            }
732        }
733        impl cljrs_gc::Trace for DoneThunk {
734            fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
735        }
736        impl Thunk for DoneThunk {
737            fn force(&self) -> Result<Value, String> {
738                Ok(Value::Long(99))
739            }
740        }
741        let ls = LazySeq::new(Box::new(DoneThunk));
742        let _ = ls.realize(); // force it
743        let v = Value::LazySeq(GcPtr::new(ls));
744        assert_eq!(roundtrip(&v), Value::Long(99));
745    }
746
747    #[test]
748    fn with_meta_roundtrip() {
749        let v = Value::Long(7).with_meta(Value::Map(MapValue::empty()));
750        let rt = roundtrip(&v);
751        // WithMeta strips for equality, so unwrap and check inner
752        assert_eq!(rt, Value::Long(7));
753    }
754
755    #[test]
756    fn reduced_roundtrip() {
757        let v = Value::Reduced(Box::new(Value::Long(55)));
758        assert_eq!(roundtrip(&v), Value::Reduced(Box::new(Value::Long(55))));
759    }
760}