Skip to main content

tidepool_bridge/
impls.rs

1use crate::error::BridgeError;
2use crate::traits::{FromCore, ToCore};
3use std::sync::{Arc, Mutex};
4use tidepool_eval::Value;
5use tidepool_repr::{DataConId, DataConTable, Literal};
6
7/// Check if a DataConId matches a known boxing constructor name (I#, W#, D#, C#).
8fn is_boxing_con(name: &str, id: DataConId, table: &DataConTable) -> bool {
9    table.get_by_name(name) == Some(id)
10}
11
12// Helper for type mismatch errors
13fn type_mismatch(expected: &str, got: &Value) -> BridgeError {
14    let got_str = match got {
15        Value::Lit(l) => format!("{:?}", l),
16        Value::Con(id, _) => format!("Con({:?})", id),
17        Value::Closure(_, _, _) => "Closure".to_string(),
18        Value::ThunkRef(_) => "ThunkRef".to_string(),
19        Value::JoinCont(_, _, _) => "JoinCont".to_string(),
20        Value::ConFun(id, arity, args) => format!("ConFun({:?}, {}/{})", id, args.len(), arity),
21        Value::ByteArray(bs) => match bs.lock() {
22            Ok(b) => format!("ByteArray(len={})", b.len()),
23            Err(_) => "ByteArray(poisoned)".to_string(),
24        },
25    };
26    BridgeError::TypeMismatch {
27        expected: expected.to_string(),
28        got: got_str,
29    }
30}
31
32impl<T> FromCore for std::marker::PhantomData<T> {
33    fn from_value(value: &Value, _table: &DataConTable) -> Result<Self, BridgeError> {
34        match value {
35            Value::Con(_, fields) if fields.is_empty() => Ok(std::marker::PhantomData),
36            Value::Con(id, fields) => Err(BridgeError::ArityMismatch {
37                con: *id,
38                expected: 0,
39                got: fields.len(),
40            }),
41            _ => Err(type_mismatch("Con", value)),
42        }
43    }
44}
45
46impl<T> ToCore for std::marker::PhantomData<T> {
47    fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
48        // We use a dummy id since PhantomData has no representation in Core
49        // but we need some Con to represent it if it's a field.
50        // Actually, in Tidepool/Haskell, PhantomData fields shouldn't exist in Core.
51        // But for the bridge to work with derived enums, we need an impl.
52        // Let's use a unit tuple id if available, or just any unit-like.
53        let id = table
54            .get_by_name("()")
55            .or_else(|| table.iter().find(|dc| dc.rep_arity == 0).map(|dc| dc.id))
56            .ok_or_else(|| BridgeError::UnknownDataConName("()".into()))?;
57        Ok(Value::Con(id, vec![]))
58    }
59}
60
61// Box
62
63// Value identity — pass through without conversion.
64impl ToCore for Value {
65    fn to_value(&self, _table: &DataConTable) -> Result<Value, BridgeError> {
66        Ok(self.clone())
67    }
68}
69
70impl FromCore for Value {
71    fn from_value(value: &Value, _table: &DataConTable) -> Result<Self, BridgeError> {
72        Ok(value.clone())
73    }
74}
75
76impl<T: FromCore> FromCore for Box<T> {
77    fn from_value(value: &Value, table: &DataConTable) -> Result<Self, BridgeError> {
78        T::from_value(value, table).map(Box::new)
79    }
80}
81
82impl<T: ToCore> ToCore for Box<T> {
83    fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
84        (**self).to_value(table)
85    }
86}
87
88// Unit
89
90impl ToCore for () {
91    fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
92        let id = table
93            .get_by_name("()")
94            .ok_or_else(|| BridgeError::UnknownDataConName("()".into()))?;
95        Ok(Value::Con(id, vec![]))
96    }
97}
98
99impl FromCore for () {
100    fn from_value(value: &Value, _table: &DataConTable) -> Result<Self, BridgeError> {
101        match value {
102            Value::Con(_, fields) if fields.is_empty() => Ok(()),
103            _ => Err(type_mismatch("()", value)),
104        }
105    }
106}
107
108// Primitives
109
110/// Bridges Rust `i64` to Haskell `Int#` literal.
111/// Also transparently unwraps `I#(n)` (boxed Int).
112impl FromCore for i64 {
113    fn from_value(value: &Value, table: &DataConTable) -> Result<Self, BridgeError> {
114        match value {
115            Value::Lit(Literal::LitInt(n)) => Ok(*n),
116            Value::Con(id, fields) if fields.len() == 1 => {
117                if is_boxing_con("I#", *id, table) {
118                    i64::from_value(&fields[0], table)
119                } else {
120                    Err(type_mismatch("LitInt or I#", value))
121                }
122            }
123            _ => Err(type_mismatch("LitInt or I#", value)),
124        }
125    }
126}
127
128impl ToCore for i64 {
129    fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
130        let id = table
131            .get_by_name("I#")
132            .ok_or_else(|| BridgeError::UnknownDataConName("I#".into()))?;
133        Ok(Value::Con(id, vec![Value::Lit(Literal::LitInt(*self))]))
134    }
135}
136
137/// Bridges Rust `u64` to Haskell `Word#` literal.
138/// Also transparently unwraps `W#(n)` (boxed Word).
139impl FromCore for u64 {
140    fn from_value(value: &Value, table: &DataConTable) -> Result<Self, BridgeError> {
141        match value {
142            Value::Lit(Literal::LitWord(n)) => Ok(*n),
143            Value::Con(id, fields) if fields.len() == 1 => {
144                if is_boxing_con("W#", *id, table) {
145                    u64::from_value(&fields[0], table)
146                } else {
147                    Err(type_mismatch("LitWord or W#", value))
148                }
149            }
150            _ => Err(type_mismatch("LitWord or W#", value)),
151        }
152    }
153}
154
155impl ToCore for u64 {
156    fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
157        let id = table
158            .get_by_name("W#")
159            .ok_or_else(|| BridgeError::UnknownDataConName("W#".into()))?;
160        Ok(Value::Con(id, vec![Value::Lit(Literal::LitWord(*self))]))
161    }
162}
163
164/// Bridges Rust `f64` to Haskell `Double#` literal.
165/// Also transparently unwraps `D#(n)` (boxed Double).
166impl FromCore for f64 {
167    fn from_value(value: &Value, table: &DataConTable) -> Result<Self, BridgeError> {
168        match value {
169            Value::Lit(Literal::LitDouble(bits)) => Ok(f64::from_bits(*bits)),
170            Value::Con(id, fields) if fields.len() == 1 => {
171                if is_boxing_con("D#", *id, table) {
172                    f64::from_value(&fields[0], table)
173                } else {
174                    Err(type_mismatch("LitDouble or D#", value))
175                }
176            }
177            _ => Err(type_mismatch("LitDouble or D#", value)),
178        }
179    }
180}
181
182impl ToCore for f64 {
183    fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
184        let id = table
185            .get_by_name("D#")
186            .ok_or_else(|| BridgeError::UnknownDataConName("D#".into()))?;
187        Ok(Value::Con(
188            id,
189            vec![Value::Lit(Literal::LitDouble(self.to_bits()))],
190        ))
191    }
192}
193
194/// Bridges Rust `i32` to Haskell `Int#` literal.
195/// Returns error on overflow/underflow.
196impl FromCore for i32 {
197    fn from_value(value: &Value, table: &DataConTable) -> Result<Self, BridgeError> {
198        let n = i64::from_value(value, table)?;
199        if n < i32::MIN as i64 || n > i32::MAX as i64 {
200            return Err(BridgeError::TypeMismatch {
201                expected: "i32 in range [-2147483648, 2147483647]".to_string(),
202                got: format!("LitInt({})", n),
203            });
204        }
205        Ok(n as i32)
206    }
207}
208
209impl ToCore for i32 {
210    fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
211        (*self as i64).to_value(table)
212    }
213}
214
215/// Bridges Rust `bool` to Haskell `Bool` (True/False constructors).
216impl FromCore for bool {
217    fn from_value(value: &Value, table: &DataConTable) -> Result<Self, BridgeError> {
218        match value {
219            Value::Con(id, fields) => {
220                let true_id = table
221                    .get_by_name("True")
222                    .ok_or(BridgeError::UnknownDataConName("True".into()))?;
223                let false_id = table
224                    .get_by_name("False")
225                    .ok_or(BridgeError::UnknownDataConName("False".into()))?;
226
227                if *id == true_id {
228                    if fields.is_empty() {
229                        Ok(true)
230                    } else {
231                        Err(BridgeError::ArityMismatch {
232                            con: *id,
233                            expected: 0,
234                            got: fields.len(),
235                        })
236                    }
237                } else if *id == false_id {
238                    if fields.is_empty() {
239                        Ok(false)
240                    } else {
241                        Err(BridgeError::ArityMismatch {
242                            con: *id,
243                            expected: 0,
244                            got: fields.len(),
245                        })
246                    }
247                } else {
248                    Err(BridgeError::UnknownDataCon(*id))
249                }
250            }
251            _ => Err(type_mismatch("Con", value)),
252        }
253    }
254}
255
256impl ToCore for bool {
257    fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
258        let name = if *self { "True" } else { "False" };
259        let id = table
260            .get_by_name(name)
261            .ok_or_else(|| BridgeError::UnknownDataConName(name.into()))?;
262        Ok(Value::Con(id, vec![]))
263    }
264}
265
266/// Also transparently unwraps `C#(c)` (boxed Char).
267impl FromCore for char {
268    fn from_value(value: &Value, table: &DataConTable) -> Result<Self, BridgeError> {
269        match value {
270            Value::Lit(Literal::LitChar(c)) => Ok(*c),
271            Value::Con(id, fields) if fields.len() == 1 => {
272                if is_boxing_con("C#", *id, table) {
273                    char::from_value(&fields[0], table)
274                } else {
275                    Err(type_mismatch("LitChar or C#", value))
276                }
277            }
278            _ => Err(type_mismatch("LitChar or C#", value)),
279        }
280    }
281}
282
283impl ToCore for char {
284    fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
285        let id = table
286            .get_by_name("C#")
287            .ok_or_else(|| BridgeError::UnknownDataConName("C#".into()))?;
288        Ok(Value::Con(id, vec![Value::Lit(Literal::LitChar(*self))]))
289    }
290}
291
292impl FromCore for String {
293    fn from_value(value: &Value, table: &DataConTable) -> Result<Self, BridgeError> {
294        match value {
295            // Text constructor: Text ByteArray# off len
296            Value::Con(id, fields)
297                if fields.len() == 3 && table.get_by_name("Text") == Some(*id) =>
298            {
299                let ba = match &fields[0] {
300                    Value::ByteArray(bs) => bs
301                        .lock()
302                        .map_err(|_| BridgeError::InternalError("mutex poisoned".into()))?
303                        .clone(),
304                    // Lifted ByteArray wrapper: Con("ByteArray", [Value::ByteArray(..)])
305                    Value::Con(ba_id, ba_fields)
306                        if ba_fields.len() == 1
307                            && table.get_by_name("ByteArray") == Some(*ba_id) =>
308                    {
309                        match &ba_fields[0] {
310                            Value::ByteArray(bs) => bs
311                                .lock()
312                                .map_err(|_| BridgeError::InternalError("mutex poisoned".into()))?
313                                .clone(),
314                            _ => {
315                                return Err(type_mismatch("ByteArray# in ByteArray", &ba_fields[0]))
316                            }
317                        }
318                    }
319                    _ => return Err(type_mismatch("ByteArray or ByteArray# in Text", &fields[0])),
320                };
321                let off = i64::from_value(&fields[1], table)? as usize;
322                let len = i64::from_value(&fields[2], table)? as usize;
323                if off + len > ba.len() {
324                    return Err(BridgeError::TypeMismatch {
325                        expected: "valid Text slice".to_string(),
326                        got: format!("off={}, len={}, ba_len={}", off, len, ba.len()),
327                    });
328                }
329                String::from_utf8(ba[off..off + len].to_vec()).map_err(|e| {
330                    BridgeError::TypeMismatch {
331                        expected: "UTF-8 Text".to_string(),
332                        got: format!("Invalid UTF-8: {}", e),
333                    }
334                })
335            }
336            Value::Lit(Literal::LitString(bytes)) => {
337                String::from_utf8(bytes.clone()).map_err(|e| BridgeError::TypeMismatch {
338                    expected: "UTF-8 String".to_string(),
339                    got: format!("Invalid UTF-8: {}", e),
340                })
341            }
342            // Also accept cons-cell list of Char (from ++ desugaring)
343            Value::Con(_, _) => {
344                let mut chars = Vec::new();
345                let mut cur = value;
346                loop {
347                    match cur {
348                        Value::Con(tag, fields)
349                            if table.get_by_name("[]") == Some(*tag) && fields.is_empty() =>
350                        {
351                            break;
352                        }
353                        Value::Con(tag, fields)
354                            if table.get_by_name(":") == Some(*tag) && fields.len() == 2 =>
355                        {
356                            match &fields[0] {
357                                Value::Lit(Literal::LitChar(c)) => chars.push(*c),
358                                // Boxing: C# wraps a Char
359                                Value::Con(box_tag, box_fields)
360                                    if table.get_by_name("C#") == Some(*box_tag)
361                                        && box_fields.len() == 1 =>
362                                {
363                                    match &box_fields[0] {
364                                        Value::Lit(Literal::LitChar(c)) => chars.push(*c),
365                                        other => return Err(type_mismatch("Char in C#", other)),
366                                    }
367                                }
368                                other => return Err(type_mismatch("Char or C#", other)),
369                            }
370                            cur = &fields[1];
371                        }
372                        _ => return Err(type_mismatch("[] or (:)", cur)),
373                    }
374                }
375                Ok(chars.into_iter().collect())
376            }
377            _ => Err(type_mismatch("Text, LitString, or [Char]", value)),
378        }
379    }
380}
381
382impl ToCore for String {
383    fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
384        let text_id = table
385            .get_by_name("Text")
386            .ok_or_else(|| BridgeError::UnknownDataConName("Text".into()))?;
387        let bytes = self.as_bytes().to_vec();
388        let len = bytes.len() as i64;
389        // GHC Core at -O2 uses the worker representation of Text with
390        // unboxed fields: Text ByteArray# Int# Int#
391        // (not the source-level Text !ByteArray !Int !Int with boxed wrappers).
392        // The JIT compiles GHC Core directly, so values injected from Rust
393        // must match the worker representation.
394        let ba_raw = Value::ByteArray(Arc::new(Mutex::new(bytes)));
395        Ok(Value::Con(
396            text_id,
397            vec![
398                ba_raw,
399                Value::Lit(Literal::LitInt(0)),
400                Value::Lit(Literal::LitInt(len)),
401            ],
402        ))
403    }
404}
405
406// Containers
407
408impl<T: FromCore> FromCore for Option<T> {
409    fn from_value(value: &Value, table: &DataConTable) -> Result<Self, BridgeError> {
410        match value {
411            Value::Con(id, fields) => {
412                let nothing_id = table
413                    .get_by_name("Nothing")
414                    .ok_or(BridgeError::UnknownDataConName("Nothing".into()))?;
415                let just_id = table
416                    .get_by_name("Just")
417                    .ok_or(BridgeError::UnknownDataConName("Just".into()))?;
418
419                if *id == nothing_id {
420                    if fields.is_empty() {
421                        Ok(None)
422                    } else {
423                        Err(BridgeError::ArityMismatch {
424                            con: *id,
425                            expected: 0,
426                            got: fields.len(),
427                        })
428                    }
429                } else if *id == just_id {
430                    if fields.len() == 1 {
431                        Ok(Some(T::from_value(&fields[0], table)?))
432                    } else {
433                        Err(BridgeError::ArityMismatch {
434                            con: *id,
435                            expected: 1,
436                            got: fields.len(),
437                        })
438                    }
439                } else {
440                    Err(BridgeError::UnknownDataCon(*id))
441                }
442            }
443            _ => Err(type_mismatch("Con", value)),
444        }
445    }
446}
447
448impl<T: ToCore> ToCore for Option<T> {
449    fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
450        match self {
451            None => {
452                let id = table
453                    .get_by_name("Nothing")
454                    .ok_or_else(|| BridgeError::UnknownDataConName("Nothing".into()))?;
455                Ok(Value::Con(id, vec![]))
456            }
457            Some(x) => {
458                let id = table
459                    .get_by_name("Just")
460                    .ok_or_else(|| BridgeError::UnknownDataConName("Just".into()))?;
461                Ok(Value::Con(id, vec![x.to_value(table)?]))
462            }
463        }
464    }
465}
466
467impl<T: FromCore> FromCore for Vec<T> {
468    fn from_value(value: &Value, table: &DataConTable) -> Result<Self, BridgeError> {
469        let nil_id = table
470            .get_by_name("[]")
471            .ok_or(BridgeError::UnknownDataConName("[]".into()))?;
472        let cons_id = table
473            .get_by_name(":")
474            .ok_or(BridgeError::UnknownDataConName(":".into()))?;
475
476        let mut res = Vec::new();
477        let mut curr = value;
478
479        loop {
480            match curr {
481                Value::Con(id, fields) => {
482                    if *id == nil_id {
483                        if fields.is_empty() {
484                            break;
485                        } else {
486                            return Err(BridgeError::ArityMismatch {
487                                con: *id,
488                                expected: 0,
489                                got: fields.len(),
490                            });
491                        }
492                    } else if *id == cons_id {
493                        if fields.len() == 2 {
494                            res.push(T::from_value(&fields[0], table)?);
495                            curr = &fields[1];
496                        } else {
497                            return Err(BridgeError::ArityMismatch {
498                                con: *id,
499                                expected: 2,
500                                got: fields.len(),
501                            });
502                        }
503                    } else {
504                        return Err(BridgeError::UnknownDataCon(*id));
505                    }
506                }
507                _ => return Err(type_mismatch("Con", curr)),
508            }
509        }
510
511        Ok(res)
512    }
513}
514
515impl<T: ToCore> ToCore for Vec<T> {
516    fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
517        let nil_id = table
518            .get_by_name("[]")
519            .ok_or_else(|| BridgeError::UnknownDataConName("[]".into()))?;
520        let cons_id = table
521            .get_by_name(":")
522            .ok_or_else(|| BridgeError::UnknownDataConName(":".into()))?;
523
524        let mut res = Value::Con(nil_id, vec![]);
525        for x in self.iter().rev() {
526            res = Value::Con(cons_id, vec![x.to_value(table)?, res]);
527        }
528        Ok(res)
529    }
530}
531
532impl<T: FromCore, E: FromCore> FromCore for Result<T, E> {
533    fn from_value(value: &Value, table: &DataConTable) -> Result<Self, BridgeError> {
534        match value {
535            Value::Con(id, fields) => {
536                let right_id = table
537                    .get_by_name("Right")
538                    .or_else(|| table.get_by_name("Ok"))
539                    .ok_or(BridgeError::UnknownDataConName("Right/Ok".into()))?;
540                let left_id = table
541                    .get_by_name("Left")
542                    .or_else(|| table.get_by_name("Err"))
543                    .ok_or(BridgeError::UnknownDataConName("Left/Err".into()))?;
544
545                if *id == right_id {
546                    if fields.len() == 1 {
547                        Ok(Ok(T::from_value(&fields[0], table)?))
548                    } else {
549                        Err(BridgeError::ArityMismatch {
550                            con: *id,
551                            expected: 1,
552                            got: fields.len(),
553                        })
554                    }
555                } else if *id == left_id {
556                    if fields.len() == 1 {
557                        Ok(Err(E::from_value(&fields[0], table)?))
558                    } else {
559                        Err(BridgeError::ArityMismatch {
560                            con: *id,
561                            expected: 1,
562                            got: fields.len(),
563                        })
564                    }
565                } else {
566                    Err(BridgeError::UnknownDataCon(*id))
567                }
568            }
569            _ => Err(type_mismatch("Con", value)),
570        }
571    }
572}
573
574impl<T: ToCore, E: ToCore> ToCore for Result<T, E> {
575    fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
576        match self {
577            Ok(x) => {
578                let id = table
579                    .get_by_name("Right")
580                    .or_else(|| table.get_by_name("Ok"))
581                    .ok_or_else(|| BridgeError::UnknownDataConName("Right/Ok".into()))?;
582                Ok(Value::Con(id, vec![x.to_value(table)?]))
583            }
584            Err(e) => {
585                let id = table
586                    .get_by_name("Left")
587                    .or_else(|| table.get_by_name("Err"))
588                    .ok_or_else(|| BridgeError::UnknownDataConName("Left/Err".into()))?;
589                Ok(Value::Con(id, vec![e.to_value(table)?]))
590            }
591        }
592    }
593}
594
595// Tuples
596
597impl<A: FromCore, B: FromCore> FromCore for (A, B) {
598    fn from_value(value: &Value, table: &DataConTable) -> Result<Self, BridgeError> {
599        match value {
600            Value::Con(id, fields) => {
601                let pair_id = table
602                    .get_by_name("(,)")
603                    .ok_or(BridgeError::UnknownDataConName("(,)".into()))?;
604                if *id == pair_id {
605                    if fields.len() == 2 {
606                        Ok((
607                            A::from_value(&fields[0], table)?,
608                            B::from_value(&fields[1], table)?,
609                        ))
610                    } else {
611                        Err(BridgeError::ArityMismatch {
612                            con: *id,
613                            expected: 2,
614                            got: fields.len(),
615                        })
616                    }
617                } else {
618                    Err(BridgeError::UnknownDataCon(*id))
619                }
620            }
621            _ => Err(type_mismatch("Con", value)),
622        }
623    }
624}
625
626impl<A: ToCore, B: ToCore> ToCore for (A, B) {
627    fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
628        let pair_id = table
629            .get_by_name("(,)")
630            .ok_or_else(|| BridgeError::UnknownDataConName("(,)".into()))?;
631        Ok(Value::Con(
632            pair_id,
633            vec![self.0.to_value(table)?, self.1.to_value(table)?],
634        ))
635    }
636}
637
638impl<A: FromCore, B: FromCore, C: FromCore> FromCore for (A, B, C) {
639    fn from_value(value: &Value, table: &DataConTable) -> Result<Self, BridgeError> {
640        match value {
641            Value::Con(id, fields) => {
642                let triple_id = table
643                    .get_by_name("(,,)")
644                    .ok_or(BridgeError::UnknownDataConName("(,,)".into()))?;
645                if *id == triple_id {
646                    if fields.len() == 3 {
647                        Ok((
648                            A::from_value(&fields[0], table)?,
649                            B::from_value(&fields[1], table)?,
650                            C::from_value(&fields[2], table)?,
651                        ))
652                    } else {
653                        Err(BridgeError::ArityMismatch {
654                            con: *id,
655                            expected: 3,
656                            got: fields.len(),
657                        })
658                    }
659                } else {
660                    Err(BridgeError::UnknownDataCon(*id))
661                }
662            }
663            _ => Err(type_mismatch("Con", value)),
664        }
665    }
666}
667
668impl<A: ToCore, B: ToCore, C: ToCore> ToCore for (A, B, C) {
669    fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
670        let triple_id = table
671            .get_by_name("(,,)")
672            .ok_or_else(|| BridgeError::UnknownDataConName("(,,)".into()))?;
673        Ok(Value::Con(
674            triple_id,
675            vec![
676                self.0.to_value(table)?,
677                self.1.to_value(table)?,
678                self.2.to_value(table)?,
679            ],
680        ))
681    }
682}
683
684#[cfg(test)]
685mod tests {
686    use super::*;
687    use tidepool_repr::{DataCon, DataConId};
688
689    fn test_table() -> DataConTable {
690        let mut t = DataConTable::new();
691        // Nothing=0, Just=1, False=2, True=3, ()=4, Nil=5, Cons=6, (,,)=7, Right=8, Left=9
692        // I#=10, W#=11, D#=12, C#=13
693        t.insert(DataCon {
694            id: DataConId(0),
695            name: "Nothing".into(),
696            tag: 1,
697            rep_arity: 0,
698            field_bangs: vec![],
699            qualified_name: None,
700        });
701        t.insert(DataCon {
702            id: DataConId(1),
703            name: "Just".into(),
704            tag: 2,
705            rep_arity: 1,
706            field_bangs: vec![],
707            qualified_name: None,
708        });
709        t.insert(DataCon {
710            id: DataConId(2),
711            name: "False".into(),
712            tag: 1,
713            rep_arity: 0,
714            field_bangs: vec![],
715            qualified_name: None,
716        });
717        t.insert(DataCon {
718            id: DataConId(3),
719            name: "True".into(),
720            tag: 2,
721            rep_arity: 0,
722            field_bangs: vec![],
723            qualified_name: None,
724        });
725        t.insert(DataCon {
726            id: DataConId(4),
727            name: "(,)".into(),
728            tag: 1,
729            rep_arity: 2,
730            field_bangs: vec![],
731            qualified_name: None,
732        });
733        t.insert(DataCon {
734            id: DataConId(5),
735            name: "[]".into(),
736            tag: 1,
737            rep_arity: 0,
738            field_bangs: vec![],
739            qualified_name: None,
740        });
741        t.insert(DataCon {
742            id: DataConId(6),
743            name: ":".into(),
744            tag: 2,
745            rep_arity: 2,
746            field_bangs: vec![],
747            qualified_name: None,
748        });
749        t.insert(DataCon {
750            id: DataConId(7),
751            name: "(,,)".into(),
752            tag: 1,
753            rep_arity: 3,
754            field_bangs: vec![],
755            qualified_name: None,
756        });
757        t.insert(DataCon {
758            id: DataConId(8),
759            name: "Right".into(),
760            tag: 2,
761            rep_arity: 1,
762            field_bangs: vec![],
763            qualified_name: None,
764        });
765        t.insert(DataCon {
766            id: DataConId(9),
767            name: "Left".into(),
768            tag: 1,
769            rep_arity: 1,
770            field_bangs: vec![],
771            qualified_name: None,
772        });
773        t.insert(DataCon {
774            id: DataConId(10),
775            name: "I#".into(),
776            tag: 1,
777            rep_arity: 1,
778            field_bangs: vec![],
779            qualified_name: None,
780        });
781        t.insert(DataCon {
782            id: DataConId(11),
783            name: "W#".into(),
784            tag: 1,
785            rep_arity: 1,
786            field_bangs: vec![],
787            qualified_name: None,
788        });
789        t.insert(DataCon {
790            id: DataConId(12),
791            name: "D#".into(),
792            tag: 1,
793            rep_arity: 1,
794            field_bangs: vec![],
795            qualified_name: None,
796        });
797        t.insert(DataCon {
798            id: DataConId(13),
799            name: "C#".into(),
800            tag: 1,
801            rep_arity: 1,
802            field_bangs: vec![],
803            qualified_name: None,
804        });
805        t.insert(DataCon {
806            id: DataConId(14),
807            name: "()".into(),
808            tag: 1,
809            rep_arity: 0,
810            field_bangs: vec![],
811            qualified_name: None,
812        });
813        t.insert(DataCon {
814            id: DataConId(15),
815            name: "Text".into(),
816            tag: 1,
817            rep_arity: 3,
818            field_bangs: vec![],
819            qualified_name: None,
820        });
821        t
822    }
823
824    fn roundtrip<T: FromCore + ToCore + PartialEq + std::fmt::Debug>(val: T, table: &DataConTable) {
825        let value = val.to_value(table).expect("ToValue failed");
826        let back = T::from_value(&value, table).expect("FromValue failed");
827        assert_eq!(val, back);
828    }
829
830    #[test]
831    fn test_i64_roundtrip() {
832        let table = test_table();
833        roundtrip(42i64, &table);
834        roundtrip(-7i64, &table);
835    }
836
837    #[test]
838    fn test_i32_roundtrip() {
839        let table = test_table();
840        roundtrip(42i32, &table);
841        roundtrip(-7i32, &table);
842    }
843
844    #[test]
845    fn test_i32_overflow() {
846        let table = test_table();
847        let val: i64 = i32::MAX as i64 + 1;
848        let value = val.to_value(&table).unwrap();
849        let res = i32::from_value(&value, &table);
850        assert!(matches!(res, Err(BridgeError::TypeMismatch { .. })));
851    }
852
853    #[test]
854    fn test_u64_roundtrip() {
855        let table = test_table();
856        roundtrip(42u64, &table);
857    }
858
859    #[test]
860    fn test_f64_roundtrip() {
861        let table = test_table();
862        roundtrip(3.14159f64, &table);
863        roundtrip(-0.0f64, &table);
864    }
865
866    #[test]
867    fn test_bool_roundtrip() {
868        let table = test_table();
869        roundtrip(true, &table);
870        roundtrip(false, &table);
871    }
872
873    #[test]
874    fn test_char_roundtrip() {
875        let table = test_table();
876        roundtrip('a', &table);
877        roundtrip('λ', &table);
878    }
879
880    #[test]
881    fn test_string_roundtrip() {
882        let table = test_table();
883        roundtrip("hello".to_string(), &table);
884        roundtrip("".to_string(), &table);
885    }
886
887    #[test]
888    fn test_option_roundtrip() {
889        let table = test_table();
890        roundtrip(Some(42i64), &table);
891        roundtrip(None::<i64>, &table);
892    }
893
894    #[test]
895    fn test_vec_roundtrip() {
896        let table = test_table();
897        roundtrip(vec![1i64, 2, 3], &table);
898        roundtrip(Vec::<i64>::new(), &table);
899    }
900
901    #[test]
902    fn test_result_roundtrip() {
903        let table = test_table();
904        roundtrip(Ok::<i64, String>(42), &table);
905        roundtrip(Err::<i64, String>("error".to_string()), &table);
906    }
907
908    #[test]
909    fn test_tuple2_roundtrip() {
910        let table = test_table();
911        roundtrip((42i64, true), &table);
912    }
913
914    #[test]
915    fn test_tuple3_roundtrip() {
916        let table = test_table();
917        roundtrip((42i64, true, "hello".to_string()), &table);
918    }
919
920    #[test]
921    fn test_nested_roundtrip() {
922        let table = test_table();
923        roundtrip(vec![Some(1i64), None, Some(3)], &table);
924        roundtrip(Some((42i64, vec![true, false])), &table);
925    }
926
927    #[test]
928    fn test_unknown_datacon() {
929        let table = test_table();
930        let value = Value::Con(DataConId(100), vec![]);
931        let res = bool::from_value(&value, &table);
932        assert!(matches!(
933            res,
934            Err(BridgeError::UnknownDataCon(DataConId(100)))
935        ));
936    }
937
938    #[test]
939    fn test_arity_mismatch() {
940        let table = test_table();
941        let true_id = table.get_by_name("True").unwrap();
942        let value = Value::Con(true_id, vec![Value::Lit(Literal::LitInt(1))]);
943        let res = bool::from_value(&value, &table);
944        assert!(matches!(res, Err(BridgeError::ArityMismatch { .. })));
945    }
946
947    #[test]
948    fn test_type_mismatch() {
949        let table = test_table();
950        let value = Value::Lit(Literal::LitInt(1));
951        let res = bool::from_value(&value, &table);
952        assert!(matches!(res, Err(BridgeError::TypeMismatch { .. })));
953    }
954
955    #[test]
956    fn test_string_unboxed_fields() {
957        let table = test_table();
958        let s = "hello".to_string();
959        let value = s.to_value(&table).expect("ToValue failed");
960
961        if let Value::Con(id, fields) = &value {
962            assert_eq!(table.name_of(*id), Some("Text"));
963            assert_eq!(fields.len(), 3);
964            // First field: ByteArray# (unboxed)
965            assert!(matches!(fields[0], Value::ByteArray(_)));
966            // Second field: Int# 0 (unboxed literal)
967            assert!(matches!(fields[1], Value::Lit(Literal::LitInt(0))));
968            // Third field: Int# len (unboxed literal)
969            assert!(matches!(fields[2], Value::Lit(Literal::LitInt(5))));
970        } else {
971            panic!("Expected Con, got {:?}", value);
972        }
973    }
974
975    #[test]
976    fn test_unit_roundtrip() {
977        let table = test_table();
978        roundtrip((), &table);
979    }
980
981    #[test]
982    fn test_f64_boxed_roundtrip() {
983        let table = test_table();
984        roundtrip(3.14159f64, &table);
985    }
986
987    #[test]
988    fn test_u64_boxed_roundtrip() {
989        let table = test_table();
990        roundtrip(42u64, &table);
991    }
992
993    #[test]
994    fn test_char_boxed_roundtrip() {
995        let table = test_table();
996        roundtrip('a', &table);
997    }
998
999    #[test]
1000    fn test_vec_string_roundtrip() {
1001        let table = test_table();
1002        roundtrip(vec!["a".to_string(), "b".to_string()], &table);
1003    }
1004
1005    #[test]
1006    fn test_option_nested_roundtrip() {
1007        let table = test_table();
1008        roundtrip(Some(vec![1i64, 2]), &table);
1009        roundtrip(None::<Vec<i64>>, &table);
1010    }
1011
1012    #[test]
1013    fn test_result_nested_roundtrip() {
1014        let table = test_table();
1015        roundtrip(Ok::<Vec<i64>, String>(vec![1, 2]), &table);
1016        roundtrip(Err::<Vec<i64>, String>("error".to_string()), &table);
1017    }
1018}