Skip to main content

tidepool_bridge/
impls.rs

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