Skip to main content

miniextendr_api/
coerce.rs

1//! Type coercion traits for converting Rust types to R native types.
2//!
3//! R has a fixed set of native scalar types:
4//! - `i32` (INTSXP) - 32-bit signed integer
5//! - `f64` (REALSXP) - 64-bit floating point
6//! - `RLogical` (LGLSXP) - logical (TRUE/FALSE/NA)
7//! - `u8` (RAWSXP) - raw bytes
8//! - `Rcomplex` (CPLXSXP) - complex numbers
9//!
10//! # Traits
11//!
12//! - [`Coerce<R>`] - infallible coercion (identity, widening)
13//! - [`TryCoerce<R>`] - fallible coercion (narrowing, overflow-possible)
14//!
15//! # Examples
16//!
17//! ```ignore
18//! use miniextendr_api::coerce::Coerce;
19//!
20//! // Scalar coercion
21//! let x: i32 = 42i8.coerce();
22//!
23//! // Element-wise slice coercion
24//! let slice: &[i8] = &[1, 2, 3];
25//! let vec: Vec<i32> = slice.coerce();
26//! ```
27
28use crate::altrep_traits::{NA_INTEGER, NA_LOGICAL, NA_REAL};
29use crate::ffi::{Rboolean, Rcomplex};
30
31/// Infallible coercion from `Self` to type `R`.
32///
33/// Implement this trait for types that can always be converted to `R`.
34/// Identity and widening conversions should use this trait.
35///
36/// Works for both scalars and element-wise on slices:
37/// - `i8::coerce() -> i32` (scalar widening)
38/// - `&[i8]::coerce() -> Vec<i32>` (element-wise)
39///
40/// # Example
41///
42/// ```ignore
43/// impl Coerce<i32> for MyType {
44///     fn coerce(self) -> i32 { ... }
45/// }
46/// ```
47pub trait Coerce<R> {
48    /// Convert `self` into `R`.
49    ///
50    /// This conversion must not fail.
51    fn coerce(self) -> R;
52}
53
54/// Fallible coercion from `Self` to type `R`.
55///
56/// Implement this trait for narrowing conversions that may overflow or lose precision.
57pub trait TryCoerce<R> {
58    /// Error returned when coercion fails.
59    type Error;
60    /// Attempt to convert `self` into `R`.
61    fn try_coerce(self) -> Result<R, Self::Error>;
62}
63
64/// Error type for coercion failures.
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum CoerceError {
67    /// The value cannot fit in the destination range.
68    Overflow,
69    /// The destination type cannot represent this value exactly.
70    PrecisionLoss,
71    /// The input was NaN and destination disallows it.
72    NaN,
73    /// Zero is not allowed by the conversion rule.
74    Zero,
75}
76
77impl std::fmt::Display for CoerceError {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        match self {
80            CoerceError::Overflow => write!(f, "value out of range"),
81            CoerceError::PrecisionLoss => write!(f, "precision loss"),
82            CoerceError::NaN => write!(f, "NaN cannot be converted"),
83            CoerceError::Zero => write!(f, "zero not allowed"),
84        }
85    }
86}
87
88impl std::error::Error for CoerceError {}
89
90// region: Blanket: Coerce implies TryCoerce
91
92impl<T, R> TryCoerce<R> for T
93where
94    T: Coerce<R>,
95{
96    type Error = std::convert::Infallible;
97
98    #[inline(always)]
99    fn try_coerce(self) -> Result<R, Self::Error> {
100        Ok(self.coerce())
101    }
102}
103// endregion
104
105// region: Identity coercions
106//
107// Note: Can't use blanket `impl<T> Coerce<T> for T` because it would conflict
108// with container coercion impls like `impl<T: Coerce<R>> Coerce<Vec<R>> for Vec<T>`.
109// Both would apply to `Vec<T>: Coerce<Vec<T>>`, causing overlap.
110
111macro_rules! impl_identity {
112    ($t:ty) => {
113        impl Coerce<$t> for $t {
114            #[inline(always)]
115            fn coerce(self) -> $t {
116                self
117            }
118        }
119    };
120}
121
122impl_identity!(i32);
123impl_identity!(f64);
124impl_identity!(Rboolean);
125impl_identity!(u8);
126impl_identity!(Rcomplex);
127// endregion
128
129// region: Widening conversions (blanket impls using marker traits)
130
131/// Blanket impl: Any type that widens to i32 can be coerced to i32.
132///
133/// This replaces individual macro invocations with a single blanket impl.
134/// Covers: i8, i16, u8, u16 (all types where T: `Into<i32>`).
135impl<T: crate::markers::WidensToI32> Coerce<i32> for T {
136    #[inline(always)]
137    fn coerce(self) -> i32 {
138        self.into()
139    }
140}
141
142/// Blanket impl: Any type that widens to f64 can be coerced to f64.
143///
144/// This replaces individual macro invocations with a single blanket impl.
145/// Covers: f32, i8, i16, i32, u8, u16, u32 (all types where T: `Into<f64>`).
146impl<T: crate::markers::WidensToF64> Coerce<f64> for T {
147    #[inline(always)]
148    fn coerce(self) -> f64 {
149        self.into()
150    }
151}
152// endregion
153
154// region: Widening from u8 to larger integer/float types
155
156impl Coerce<i64> for u8 {
157    #[inline(always)]
158    fn coerce(self) -> i64 {
159        self.into()
160    }
161}
162
163impl Coerce<isize> for u8 {
164    #[inline(always)]
165    fn coerce(self) -> isize {
166        self.into()
167    }
168}
169
170impl Coerce<u64> for u8 {
171    #[inline(always)]
172    fn coerce(self) -> u64 {
173        self.into()
174    }
175}
176
177impl Coerce<usize> for u8 {
178    #[inline(always)]
179    fn coerce(self) -> usize {
180        self.into()
181    }
182}
183
184impl Coerce<f32> for u8 {
185    #[inline(always)]
186    fn coerce(self) -> f32 {
187        self.into()
188    }
189}
190
191impl Coerce<f32> for i32 {
192    #[inline(always)]
193    fn coerce(self) -> f32 {
194        self as f32
195    }
196}
197// endregion
198
199// region: bool coercions
200
201impl Coerce<Rboolean> for bool {
202    #[inline(always)]
203    fn coerce(self) -> Rboolean {
204        if self {
205            Rboolean::TRUE
206        } else {
207            Rboolean::FALSE
208        }
209    }
210}
211
212impl Coerce<i32> for bool {
213    #[inline(always)]
214    fn coerce(self) -> i32 {
215        if self { 1 } else { 0 }
216    }
217}
218
219impl Coerce<f64> for bool {
220    #[inline(always)]
221    fn coerce(self) -> f64 {
222        if self { 1.0 } else { 0.0 }
223    }
224}
225
226impl Coerce<i32> for Rboolean {
227    #[inline(always)]
228    fn coerce(self) -> i32 {
229        self as i32
230    }
231}
232// endregion
233
234// region: Option<T> to R-native with None → NA
235
236/// `Option<f64>` → `f64` with `None` → `NA_real_`.
237impl Coerce<f64> for Option<f64> {
238    #[inline(always)]
239    fn coerce(self) -> f64 {
240        self.unwrap_or(NA_REAL)
241    }
242}
243
244/// `Option<i32>` → `i32` with `None` → `NA_integer_`.
245impl Coerce<i32> for Option<i32> {
246    #[inline(always)]
247    fn coerce(self) -> i32 {
248        self.unwrap_or(NA_INTEGER)
249    }
250}
251
252/// `Option<bool>` → `i32` with `None` → `NA_LOGICAL`.
253impl Coerce<i32> for Option<bool> {
254    #[inline(always)]
255    fn coerce(self) -> i32 {
256        match self {
257            Some(true) => 1,
258            Some(false) => 0,
259            None => NA_LOGICAL,
260        }
261    }
262}
263
264/// `Option<Rboolean>` → `i32` with `None` → `NA_LOGICAL`.
265impl Coerce<i32> for Option<Rboolean> {
266    #[inline(always)]
267    fn coerce(self) -> i32 {
268        match self {
269            Some(v) => v as i32,
270            None => NA_LOGICAL,
271        }
272    }
273}
274// endregion
275
276// region: i32 to larger/unsigned types (for argument coercion from R integers)
277
278/// i32 -> i64: widening, always safe
279impl Coerce<i64> for i32 {
280    #[inline(always)]
281    fn coerce(self) -> i64 {
282        self.into()
283    }
284}
285
286/// i32 -> isize: always safe (isize is at least 32 bits)
287impl Coerce<isize> for i32 {
288    #[inline(always)]
289    fn coerce(self) -> isize {
290        self as isize
291    }
292}
293
294/// i32 -> u32: can fail if negative
295impl TryCoerce<u32> for i32 {
296    type Error = CoerceError;
297
298    #[inline]
299    fn try_coerce(self) -> Result<u32, CoerceError> {
300        self.try_into().map_err(|_| CoerceError::Overflow)
301    }
302}
303
304/// i32 -> u64: can fail if negative
305impl TryCoerce<u64> for i32 {
306    type Error = CoerceError;
307
308    #[inline]
309    fn try_coerce(self) -> Result<u64, CoerceError> {
310        self.try_into().map_err(|_| CoerceError::Overflow)
311    }
312}
313
314/// i32 -> usize: can fail if negative
315impl TryCoerce<usize> for i32 {
316    type Error = CoerceError;
317
318    #[inline]
319    fn try_coerce(self) -> Result<usize, CoerceError> {
320        self.try_into().map_err(|_| CoerceError::Overflow)
321    }
322}
323// endregion
324
325// region: NonZero conversions (fallible - zero check)
326
327use core::num::{
328    NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroIsize, NonZeroU8, NonZeroU16, NonZeroU32,
329    NonZeroU64, NonZeroUsize,
330};
331
332macro_rules! impl_nonzero_from_self {
333    ($base:ty, $nz:ty) => {
334        impl TryCoerce<$nz> for $base {
335            type Error = CoerceError;
336
337            #[inline]
338            fn try_coerce(self) -> Result<$nz, CoerceError> {
339                <$nz>::new(self).ok_or(CoerceError::Zero)
340            }
341        }
342    };
343}
344
345// Direct NonZero conversions (same base type)
346impl_nonzero_from_self!(i8, NonZeroI8);
347impl_nonzero_from_self!(i16, NonZeroI16);
348impl_nonzero_from_self!(i32, NonZeroI32);
349impl_nonzero_from_self!(i64, NonZeroI64);
350impl_nonzero_from_self!(isize, NonZeroIsize);
351impl_nonzero_from_self!(u8, NonZeroU8);
352impl_nonzero_from_self!(u16, NonZeroU16);
353impl_nonzero_from_self!(u32, NonZeroU32);
354impl_nonzero_from_self!(u64, NonZeroU64);
355impl_nonzero_from_self!(usize, NonZeroUsize);
356
357/// i32 -> NonZeroI64: widen then check zero
358impl TryCoerce<NonZeroI64> for i32 {
359    type Error = CoerceError;
360
361    #[inline]
362    fn try_coerce(self) -> Result<NonZeroI64, CoerceError> {
363        NonZeroI64::new(self.into()).ok_or(CoerceError::Zero)
364    }
365}
366
367/// i32 -> NonZeroIsize: widen then check zero
368impl TryCoerce<NonZeroIsize> for i32 {
369    type Error = CoerceError;
370
371    #[inline]
372    fn try_coerce(self) -> Result<NonZeroIsize, CoerceError> {
373        NonZeroIsize::new(self as isize).ok_or(CoerceError::Zero)
374    }
375}
376
377/// i32 -> NonZeroU32: check non-negative and non-zero
378impl TryCoerce<NonZeroU32> for i32 {
379    type Error = CoerceError;
380
381    #[inline]
382    fn try_coerce(self) -> Result<NonZeroU32, CoerceError> {
383        let u: u32 = self.try_into().map_err(|_| CoerceError::Overflow)?;
384        NonZeroU32::new(u).ok_or(CoerceError::Zero)
385    }
386}
387
388/// i32 -> NonZeroU64: check non-negative and non-zero
389impl TryCoerce<NonZeroU64> for i32 {
390    type Error = CoerceError;
391
392    #[inline]
393    fn try_coerce(self) -> Result<NonZeroU64, CoerceError> {
394        let u: u64 = self.try_into().map_err(|_| CoerceError::Overflow)?;
395        NonZeroU64::new(u).ok_or(CoerceError::Zero)
396    }
397}
398
399/// i32 -> NonZeroUsize: check non-negative and non-zero
400impl TryCoerce<NonZeroUsize> for i32 {
401    type Error = CoerceError;
402
403    #[inline]
404    fn try_coerce(self) -> Result<NonZeroUsize, CoerceError> {
405        let u: usize = self.try_into().map_err(|_| CoerceError::Overflow)?;
406        NonZeroUsize::new(u).ok_or(CoerceError::Zero)
407    }
408}
409
410/// i32 -> NonZeroI8: narrow then check zero
411impl TryCoerce<NonZeroI8> for i32 {
412    type Error = CoerceError;
413
414    #[inline]
415    fn try_coerce(self) -> Result<NonZeroI8, CoerceError> {
416        let n: i8 = self.try_into().map_err(|_| CoerceError::Overflow)?;
417        NonZeroI8::new(n).ok_or(CoerceError::Zero)
418    }
419}
420
421/// i32 -> NonZeroI16: narrow then check zero
422impl TryCoerce<NonZeroI16> for i32 {
423    type Error = CoerceError;
424
425    #[inline]
426    fn try_coerce(self) -> Result<NonZeroI16, CoerceError> {
427        let n: i16 = self.try_into().map_err(|_| CoerceError::Overflow)?;
428        NonZeroI16::new(n).ok_or(CoerceError::Zero)
429    }
430}
431
432/// i32 -> NonZeroU8: check non-negative, narrow, then check zero
433impl TryCoerce<NonZeroU8> for i32 {
434    type Error = CoerceError;
435
436    #[inline]
437    fn try_coerce(self) -> Result<NonZeroU8, CoerceError> {
438        let u: u8 = self.try_into().map_err(|_| CoerceError::Overflow)?;
439        NonZeroU8::new(u).ok_or(CoerceError::Zero)
440    }
441}
442
443/// i32 -> NonZeroU16: check non-negative, narrow, then check zero
444impl TryCoerce<NonZeroU16> for i32 {
445    type Error = CoerceError;
446
447    #[inline]
448    fn try_coerce(self) -> Result<NonZeroU16, CoerceError> {
449        let u: u16 = self.try_into().map_err(|_| CoerceError::Overflow)?;
450        NonZeroU16::new(u).ok_or(CoerceError::Zero)
451    }
452}
453// endregion
454
455// region: i32/Rboolean to bool (fallible - NA handling)
456
457/// Error type for logical coercion failures.
458#[derive(Debug, Clone, Copy, PartialEq, Eq)]
459pub enum LogicalCoerceError {
460    /// R's NA_LOGICAL cannot be represented as Rust bool
461    NAValue,
462    /// Value is not 0 or 1
463    InvalidValue(i32),
464}
465
466impl std::fmt::Display for LogicalCoerceError {
467    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
468        match self {
469            LogicalCoerceError::NAValue => write!(f, "NA cannot be converted to bool"),
470            LogicalCoerceError::InvalidValue(v) => write!(f, "invalid logical value: {}", v),
471        }
472    }
473}
474
475impl std::error::Error for LogicalCoerceError {}
476
477impl TryCoerce<bool> for i32 {
478    type Error = LogicalCoerceError;
479
480    #[inline]
481    fn try_coerce(self) -> Result<bool, LogicalCoerceError> {
482        match self {
483            0 => Ok(false),
484            1 => Ok(true),
485            // NA_LOGICAL is i32::MIN in R
486            i32::MIN => Err(LogicalCoerceError::NAValue),
487            other => Err(LogicalCoerceError::InvalidValue(other)),
488        }
489    }
490}
491
492impl TryCoerce<bool> for Rboolean {
493    type Error = LogicalCoerceError;
494
495    #[inline]
496    fn try_coerce(self) -> Result<bool, LogicalCoerceError> {
497        (self as i32).try_coerce()
498    }
499}
500
501impl TryCoerce<bool> for crate::ffi::RLogical {
502    type Error = LogicalCoerceError;
503
504    #[inline]
505    fn try_coerce(self) -> Result<bool, LogicalCoerceError> {
506        self.to_i32().try_coerce()
507    }
508}
509// endregion
510
511// region: Narrowing to i32 (fallible)
512
513macro_rules! impl_try_i32 {
514    ($t:ty) => {
515        impl TryCoerce<i32> for $t {
516            type Error = CoerceError;
517            #[inline]
518            fn try_coerce(self) -> Result<i32, CoerceError> {
519                self.try_into().map_err(|_| CoerceError::Overflow)
520            }
521        }
522    };
523}
524
525impl_try_i32!(u32);
526impl_try_i32!(u64);
527impl_try_i32!(usize);
528impl_try_i32!(i64);
529impl_try_i32!(isize);
530// endregion
531
532// region: Narrowing to u8 (fallible)
533
534macro_rules! impl_try_u8 {
535    ($t:ty) => {
536        impl TryCoerce<u8> for $t {
537            type Error = CoerceError;
538            #[inline]
539            fn try_coerce(self) -> Result<u8, CoerceError> {
540                self.try_into().map_err(|_| CoerceError::Overflow)
541            }
542        }
543    };
544}
545
546impl_try_u8!(i8);
547impl_try_u8!(i16);
548impl_try_u8!(i32);
549impl_try_u8!(i64);
550impl_try_u8!(u16);
551impl_try_u8!(u32);
552impl_try_u8!(u64);
553impl_try_u8!(usize);
554impl_try_u8!(isize);
555// endregion
556
557// region: Widening to u16/i16/u32 (infallible)
558
559impl Coerce<u16> for u8 {
560    #[inline(always)]
561    fn coerce(self) -> u16 {
562        self.into()
563    }
564}
565
566impl Coerce<i16> for i8 {
567    #[inline(always)]
568    fn coerce(self) -> i16 {
569        self.into()
570    }
571}
572
573impl Coerce<i16> for u8 {
574    #[inline(always)]
575    fn coerce(self) -> i16 {
576        self.into()
577    }
578}
579
580impl Coerce<u32> for u8 {
581    #[inline(always)]
582    fn coerce(self) -> u32 {
583        self.into()
584    }
585}
586
587impl Coerce<u32> for u16 {
588    #[inline(always)]
589    fn coerce(self) -> u32 {
590        self.into()
591    }
592}
593// endregion
594
595// region: Narrowing to u16 (fallible)
596
597macro_rules! impl_try_u16 {
598    ($t:ty) => {
599        impl TryCoerce<u16> for $t {
600            type Error = CoerceError;
601            #[inline]
602            fn try_coerce(self) -> Result<u16, CoerceError> {
603                self.try_into().map_err(|_| CoerceError::Overflow)
604            }
605        }
606    };
607}
608
609impl_try_u16!(i8);
610impl_try_u16!(i16);
611impl_try_u16!(i32);
612impl_try_u16!(i64);
613impl_try_u16!(u32);
614impl_try_u16!(u64);
615impl_try_u16!(usize);
616impl_try_u16!(isize);
617// endregion
618
619// region: Narrowing to i16 (fallible)
620
621macro_rules! impl_try_i16 {
622    ($t:ty) => {
623        impl TryCoerce<i16> for $t {
624            type Error = CoerceError;
625            #[inline]
626            fn try_coerce(self) -> Result<i16, CoerceError> {
627                self.try_into().map_err(|_| CoerceError::Overflow)
628            }
629        }
630    };
631}
632
633impl_try_i16!(i32);
634impl_try_i16!(i64);
635impl_try_i16!(u16);
636impl_try_i16!(u32);
637impl_try_i16!(u64);
638impl_try_i16!(usize);
639impl_try_i16!(isize);
640// endregion
641
642// region: Narrowing to i8 (fallible)
643
644macro_rules! impl_try_i8 {
645    ($t:ty) => {
646        impl TryCoerce<i8> for $t {
647            type Error = CoerceError;
648            #[inline]
649            fn try_coerce(self) -> Result<i8, CoerceError> {
650                self.try_into().map_err(|_| CoerceError::Overflow)
651            }
652        }
653    };
654}
655
656impl_try_i8!(i16);
657impl_try_i8!(i32);
658impl_try_i8!(i64);
659impl_try_i8!(u8);
660impl_try_i8!(u16);
661impl_try_i8!(u32);
662impl_try_i8!(u64);
663impl_try_i8!(usize);
664impl_try_i8!(isize);
665// endregion
666
667// region: Float to smaller integers (fallible)
668
669impl TryCoerce<u16> for f64 {
670    type Error = CoerceError;
671
672    #[inline]
673    fn try_coerce(self) -> Result<u16, CoerceError> {
674        if self.is_nan() {
675            return Err(CoerceError::NaN);
676        }
677        if self.is_infinite() {
678            return Err(CoerceError::Overflow);
679        }
680        if self < 0.0 || self > u16::MAX as f64 {
681            return Err(CoerceError::Overflow);
682        }
683        if self.fract() != 0.0 {
684            return Err(CoerceError::PrecisionLoss);
685        }
686        Ok(self as u16)
687    }
688}
689
690impl TryCoerce<i16> for f64 {
691    type Error = CoerceError;
692
693    #[inline]
694    fn try_coerce(self) -> Result<i16, CoerceError> {
695        if self.is_nan() {
696            return Err(CoerceError::NaN);
697        }
698        if self.is_infinite() {
699            return Err(CoerceError::Overflow);
700        }
701        if self < i16::MIN as f64 || self > i16::MAX as f64 {
702            return Err(CoerceError::Overflow);
703        }
704        if self.fract() != 0.0 {
705            return Err(CoerceError::PrecisionLoss);
706        }
707        Ok(self as i16)
708    }
709}
710
711impl TryCoerce<i8> for f64 {
712    type Error = CoerceError;
713
714    #[inline]
715    fn try_coerce(self) -> Result<i8, CoerceError> {
716        if self.is_nan() {
717            return Err(CoerceError::NaN);
718        }
719        if self.is_infinite() {
720            return Err(CoerceError::Overflow);
721        }
722        if self < i8::MIN as f64 || self > i8::MAX as f64 {
723            return Err(CoerceError::Overflow);
724        }
725        if self.fract() != 0.0 {
726            return Err(CoerceError::PrecisionLoss);
727        }
728        Ok(self as i8)
729    }
730}
731// endregion
732
733// region: Float to i32 (fallible)
734
735impl TryCoerce<i32> for f64 {
736    type Error = CoerceError;
737
738    #[inline]
739    fn try_coerce(self) -> Result<i32, CoerceError> {
740        if self.is_nan() {
741            return Err(CoerceError::NaN);
742        }
743        if self.is_infinite() {
744            return Err(CoerceError::Overflow);
745        }
746        if self < i32::MIN as f64 || self > i32::MAX as f64 {
747            return Err(CoerceError::Overflow);
748        }
749        if self.fract() != 0.0 {
750            return Err(CoerceError::PrecisionLoss);
751        }
752        Ok(self as i32)
753    }
754}
755
756impl TryCoerce<i32> for f32 {
757    type Error = CoerceError;
758
759    #[inline]
760    fn try_coerce(self) -> Result<i32, CoerceError> {
761        f64::from(self).try_coerce()
762    }
763}
764
765// f64 → f32 narrowing (always succeeds, may lose precision or become inf)
766impl Coerce<f32> for f64 {
767    #[inline(always)]
768    fn coerce(self) -> f32 {
769        self as f32
770    }
771}
772// endregion
773
774// region: Float to u8 (fallible) - for RAWSXP
775
776impl TryCoerce<u8> for f64 {
777    type Error = CoerceError;
778
779    #[inline]
780    fn try_coerce(self) -> Result<u8, CoerceError> {
781        if self.is_nan() {
782            return Err(CoerceError::NaN);
783        }
784        if self.is_infinite() {
785            return Err(CoerceError::Overflow);
786        }
787        if self < 0.0 || self > u8::MAX as f64 {
788            return Err(CoerceError::Overflow);
789        }
790        if self.fract() != 0.0 {
791            return Err(CoerceError::PrecisionLoss);
792        }
793        Ok(self as u8)
794    }
795}
796
797impl TryCoerce<u8> for f32 {
798    type Error = CoerceError;
799
800    #[inline]
801    fn try_coerce(self) -> Result<u8, CoerceError> {
802        f64::from(self).try_coerce()
803    }
804}
805// endregion
806
807// region: Float to u32 (fallible)
808
809impl TryCoerce<u32> for f64 {
810    type Error = CoerceError;
811
812    #[inline]
813    fn try_coerce(self) -> Result<u32, CoerceError> {
814        if self.is_nan() {
815            return Err(CoerceError::NaN);
816        }
817        if self.is_infinite() {
818            return Err(CoerceError::Overflow);
819        }
820        if self < 0.0 || self > u32::MAX as f64 {
821            return Err(CoerceError::Overflow);
822        }
823        if self.fract() != 0.0 {
824            return Err(CoerceError::PrecisionLoss);
825        }
826        Ok(self as u32)
827    }
828}
829// endregion
830
831// region: Float to i64/u64 (fallible)
832//
833// These conversions validate that the f64 can be exactly represented as an integer.
834//
835// **Checks performed:**
836// - NaN → `Err(CoerceError::NaN)`
837// - Infinity → `Err(CoerceError::Overflow)`
838// - Out of range → `Err(CoerceError::Overflow)`
839// - Has fractional part → `Err(CoerceError::PrecisionLoss)`
840//
841// **Note on precision:**
842// f64 can exactly represent all integers in [-2^53, 2^53]. For values in this
843// range that pass the fractional check, conversion is exact. For larger f64
844// values (which must have been created through approximation), the conversion
845// returns whatever integer the f64 represents, which may not be what was
846// originally intended.
847
848/// Convert `f64` to `i64`, validating exact representation.
849impl TryCoerce<i64> for f64 {
850    type Error = CoerceError;
851
852    #[inline]
853    fn try_coerce(self) -> Result<i64, CoerceError> {
854        if self.is_nan() {
855            return Err(CoerceError::NaN);
856        }
857        if self.is_infinite() {
858            return Err(CoerceError::Overflow);
859        }
860        // i64::MIN/MAX can't be exactly represented in f64, so use safe bounds
861        if self < i64::MIN as f64 || self >= i64::MAX as f64 {
862            return Err(CoerceError::Overflow);
863        }
864        if self.fract() != 0.0 {
865            return Err(CoerceError::PrecisionLoss);
866        }
867        Ok(self as i64)
868    }
869}
870
871impl TryCoerce<u64> for f64 {
872    type Error = CoerceError;
873
874    #[inline]
875    fn try_coerce(self) -> Result<u64, CoerceError> {
876        if self.is_nan() {
877            return Err(CoerceError::NaN);
878        }
879        if self.is_infinite() {
880            return Err(CoerceError::Overflow);
881        }
882        if self < 0.0 || self >= u64::MAX as f64 {
883            return Err(CoerceError::Overflow);
884        }
885        if self.fract() != 0.0 {
886            return Err(CoerceError::PrecisionLoss);
887        }
888        Ok(self as u64)
889    }
890}
891
892impl TryCoerce<isize> for f64 {
893    type Error = CoerceError;
894
895    #[inline]
896    fn try_coerce(self) -> Result<isize, CoerceError> {
897        if self.is_nan() {
898            return Err(CoerceError::NaN);
899        }
900        if self.is_infinite() {
901            return Err(CoerceError::Overflow);
902        }
903        // Upper check uses >= because isize::MAX as f64 rounds up on 64-bit
904        if self < isize::MIN as f64 || self >= isize::MAX as f64 {
905            return Err(CoerceError::Overflow);
906        }
907        if self.fract() != 0.0 {
908            return Err(CoerceError::PrecisionLoss);
909        }
910        Ok(self as isize)
911    }
912}
913
914impl TryCoerce<usize> for f64 {
915    type Error = CoerceError;
916
917    #[inline]
918    fn try_coerce(self) -> Result<usize, CoerceError> {
919        if self.is_nan() {
920            return Err(CoerceError::NaN);
921        }
922        if self.is_infinite() {
923            return Err(CoerceError::Overflow);
924        }
925        // Upper check uses >= because usize::MAX as f64 rounds up on 64-bit
926        if self < 0.0 || self >= usize::MAX as f64 {
927            return Err(CoerceError::Overflow);
928        }
929        if self.fract() != 0.0 {
930            return Err(CoerceError::PrecisionLoss);
931        }
932        Ok(self as usize)
933    }
934}
935// endregion
936
937// region: Large int to f64 (fallible - precision)
938//
939// These conversions only succeed if the integer can be exactly represented
940// in f64. This is stricter than Rust's `as f64` which silently rounds.
941//
942// **Safe integer range:**
943// - f64 has 53 bits of mantissa precision
944// - Integers in [-2^53, 2^53] (±9,007,199,254,740,992) are exactly representable
945// - Outside this range: `Err(CoerceError::PrecisionLoss)`
946//
947// **Use cases:**
948// - Validating R function inputs won't lose precision
949// - Checked conversions in data pipelines
950// - Ensuring round-trip fidelity (i64 → R → i64)
951
952/// Convert `i64` to `f64`, failing if precision would be lost.
953///
954/// Only succeeds for values in [-2^53, 2^53].
955impl TryCoerce<f64> for i64 {
956    type Error = CoerceError;
957
958    #[inline]
959    fn try_coerce(self) -> Result<f64, CoerceError> {
960        const MAX_SAFE: i64 = 1 << 53;
961        const MIN_SAFE: i64 = -(1 << 53);
962        if !(MIN_SAFE..=MAX_SAFE).contains(&self) {
963            return Err(CoerceError::PrecisionLoss);
964        }
965        Ok(self as f64)
966    }
967}
968
969/// Convert `u64` to `f64`, failing if precision would be lost.
970///
971/// Only succeeds for values ≤ 2^53.
972impl TryCoerce<f64> for u64 {
973    type Error = CoerceError;
974
975    #[inline]
976    fn try_coerce(self) -> Result<f64, CoerceError> {
977        const MAX_SAFE: u64 = 1 << 53;
978        if self > MAX_SAFE {
979            return Err(CoerceError::PrecisionLoss);
980        }
981        Ok(self as f64)
982    }
983}
984
985impl TryCoerce<f64> for isize {
986    type Error = CoerceError;
987    #[inline]
988    fn try_coerce(self) -> Result<f64, CoerceError> {
989        (self as i64).try_coerce()
990    }
991}
992
993impl TryCoerce<f64> for usize {
994    type Error = CoerceError;
995    #[inline]
996    fn try_coerce(self) -> Result<f64, CoerceError> {
997        (self as u64).try_coerce()
998    }
999}
1000// endregion
1001
1002// region: Coerced wrapper type
1003
1004use std::marker::PhantomData;
1005
1006/// Wrapper for values coerced from an R native type during conversion.
1007///
1008/// This enables using non-native Rust types in collections read from R:
1009///
1010/// ```ignore
1011/// // Read a Vec of i64 from R integers (i32)
1012/// let vec: Vec<Coerced<i64, i32>> = TryFromSexp::try_from_sexp(sexp)?;
1013///
1014/// // Extract the values
1015/// let i64_vec: Vec<i64> = vec.into_iter().map(Coerced::into_inner).collect();
1016/// ```
1017///
1018/// The type parameters are:
1019/// - `T`: The target Rust type you want
1020/// - `R`: The R-native type to read and coerce from
1021#[repr(transparent)]
1022#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1023pub struct Coerced<T, R> {
1024    value: T,
1025    _marker: PhantomData<R>,
1026}
1027
1028impl<T, R> Coerced<T, R> {
1029    /// Create a new Coerced wrapper.
1030    #[inline]
1031    pub const fn new(value: T) -> Self {
1032        Self {
1033            value,
1034            _marker: PhantomData,
1035        }
1036    }
1037
1038    /// Extract the inner value.
1039    #[inline]
1040    pub fn into_inner(self) -> T {
1041        self.value
1042    }
1043
1044    /// Get a reference to the inner value.
1045    #[inline]
1046    pub const fn as_inner(&self) -> &T {
1047        &self.value
1048    }
1049
1050    /// Get a mutable reference to the inner value.
1051    #[inline]
1052    pub fn as_inner_mut(&mut self) -> &mut T {
1053        &mut self.value
1054    }
1055}
1056
1057impl<T, R> std::ops::Deref for Coerced<T, R> {
1058    type Target = T;
1059
1060    #[inline]
1061    fn deref(&self) -> &Self::Target {
1062        &self.value
1063    }
1064}
1065
1066impl<T, R> std::ops::DerefMut for Coerced<T, R> {
1067    #[inline]
1068    fn deref_mut(&mut self) -> &mut Self::Target {
1069        &mut self.value
1070    }
1071}
1072// endregion
1073
1074// region: Slice coercions (element-wise)
1075
1076/// Coerce a slice element-wise to a Vec.
1077impl<T: Copy + Coerce<R>, R> Coerce<Vec<R>> for &[T] {
1078    #[inline]
1079    fn coerce(self) -> Vec<R> {
1080        self.iter().copied().map(Coerce::coerce).collect()
1081    }
1082}
1083
1084/// Coerce a Vec element-wise to a new Vec.
1085impl<T: Coerce<R>, R> Coerce<Vec<R>> for Vec<T> {
1086    #[inline]
1087    fn coerce(self) -> Vec<R> {
1088        self.into_iter().map(Coerce::coerce).collect()
1089    }
1090}
1091
1092/// Infallible element-wise coercion for `Box<[T]>` → `Box<[R]>`.
1093impl<T: Coerce<R>, R> Coerce<Box<[R]>> for Box<[T]> {
1094    #[inline]
1095    fn coerce(self) -> Box<[R]> {
1096        Vec::from(self).into_iter().map(Coerce::coerce).collect()
1097    }
1098}
1099
1100/// Infallible element-wise coercion for VecDeque to VecDeque.
1101impl<T: Coerce<R>, R> Coerce<std::collections::VecDeque<R>> for std::collections::VecDeque<T> {
1102    fn coerce(self) -> std::collections::VecDeque<R> {
1103        self.into_iter().map(Coerce::coerce).collect()
1104    }
1105}
1106
1107// Note: TryCoerce<Vec<R>> is automatically provided by the blanket impl
1108// `impl<T: Coerce<R>> TryCoerce<R> for T`. For types that only implement
1109// TryCoerce (not Coerce), use manual iteration:
1110// slice.iter().map(|x| x.try_coerce()).collect::<Result<Vec<_>, _>>()
1111// endregion
1112
1113// region: TinyVec coercions (element-wise)
1114
1115#[cfg(feature = "tinyvec")]
1116/// Element-wise coercion for TinyVec.
1117///
1118/// Enables conversions like `TinyVec<[i8; 10]>` → `TinyVec<[i32; 10]>` via widening.
1119impl<T, R, const N: usize> Coerce<tinyvec::TinyVec<[R; N]>> for tinyvec::TinyVec<[T; N]>
1120where
1121    T: Coerce<R>,
1122    [T; N]: tinyvec::Array<Item = T>,
1123    [R; N]: tinyvec::Array<Item = R>,
1124{
1125    fn coerce(self) -> tinyvec::TinyVec<[R; N]> {
1126        self.into_iter().map(Coerce::coerce).collect()
1127    }
1128}
1129
1130#[cfg(feature = "tinyvec")]
1131/// Element-wise coercion for ArrayVec.
1132///
1133/// Enables conversions like `ArrayVec<[i8; 10]>` → `ArrayVec<[i32; 10]>` via widening.
1134impl<T, R, const N: usize> Coerce<tinyvec::ArrayVec<[R; N]>> for tinyvec::ArrayVec<[T; N]>
1135where
1136    T: Coerce<R>,
1137    [T; N]: tinyvec::Array<Item = T>,
1138    [R; N]: tinyvec::Array<Item = R>,
1139{
1140    fn coerce(self) -> tinyvec::ArrayVec<[R; N]> {
1141        self.into_iter().map(Coerce::coerce).collect()
1142    }
1143}
1144// endregion
1145
1146// region: Tuple coercions (element-wise)
1147
1148/// Macro to implement element-wise Coerce for tuples.
1149macro_rules! impl_tuple_coerce {
1150    (($($T:ident),+), ($($R:ident),+), ($($idx:tt),+)) => {
1151        impl<$($T,)+ $($R,)+> Coerce<($($R,)+)> for ($($T,)+)
1152        where
1153            $($T: Coerce<$R>,)+
1154        {
1155            #[inline]
1156            fn coerce(self) -> ($($R,)+) {
1157                ($(Coerce::<$R>::coerce(self.$idx),)+)
1158            }
1159        }
1160    };
1161}
1162
1163// Implement for tuples of sizes 2-8
1164impl_tuple_coerce!((A, B), (RA, RB), (0, 1));
1165impl_tuple_coerce!((A, B, C), (RA, RB, RC), (0, 1, 2));
1166impl_tuple_coerce!((A, B, C, D), (RA, RB, RC, RD), (0, 1, 2, 3));
1167impl_tuple_coerce!((A, B, C, D, E), (RA, RB, RC, RD, RE), (0, 1, 2, 3, 4));
1168impl_tuple_coerce!(
1169    (A, B, C, D, E, F),
1170    (RA, RB, RC, RD, RE, RF),
1171    (0, 1, 2, 3, 4, 5)
1172);
1173impl_tuple_coerce!(
1174    (A, B, C, D, E, F, G),
1175    (RA, RB, RC, RD, RE, RF, RG),
1176    (0, 1, 2, 3, 4, 5, 6)
1177);
1178impl_tuple_coerce!(
1179    (A, B, C, D, E, F, G, H),
1180    (RA, RB, RC, RD, RE, RF, RG, RH),
1181    (0, 1, 2, 3, 4, 5, 6, 7)
1182);
1183// endregion
1184
1185// region: Tests
1186
1187#[cfg(test)]
1188mod tests;
1189// endregion