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