Skip to main content

miniextendr_api/
into_r_as.rs

1//! Storage-directed conversion to R.
2//!
3//! This module provides [`IntoRAs`], a trait for converting Rust values to R SEXPs
4//! with explicit target storage type selection.
5//!
6//! # Value-Based Semantics
7//!
8//! Conversions are **runtime-checked**: if the actual value fits the target type,
9//! it converts; if not, it errors. There is no "lossy" escape hatch - if you want
10//! lossy conversion, cast the values yourself first.
11//!
12//! # Example
13//!
14//! ```ignore
15//! use miniextendr_api::IntoRAs;
16//!
17//! // These succeed (values fit)
18//! let x = vec![1_i64, 2, 3];
19//! let sexp = x.into_r_as::<i32>()?;           // OK: all values in i32 range
20//!
21//! let y = vec![1.0_f64, 2.0, 3.0];
22//! let sexp = y.into_r_as::<i32>()?;           // OK: all values are integral
23//!
24//! // These fail (values don't fit)
25//! let z = vec![1_i64 << 40];
26//! let sexp = z.into_r_as::<i32>()?;           // Error: out of range
27//!
28//! let w = vec![1.5_f64];
29//! let sexp = w.into_r_as::<i32>()?;           // Error: not integral
30//!
31//! // User wants lossy? Cast first.
32//! let lossy: Vec<i32> = vec![1.5_f64, 2.7].iter().map(|&x| x as i32).collect();
33//! let sexp = lossy.into_r();                  // [1, 2] - user's responsibility
34//! ```
35
36use crate::coerce::{CoerceError, TryCoerce};
37use crate::ffi::{RLogical, SEXP};
38use crate::into_r::IntoR;
39use std::fmt;
40
41// region: Error type
42
43/// Error type for storage-directed conversion failures.
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum StorageCoerceError {
46    /// Conversion between these types is not supported.
47    Unsupported {
48        /// Source Rust type name.
49        from: &'static str,
50        /// Target storage type name.
51        to: &'static str,
52    },
53    /// Value is out of range for the target type.
54    OutOfRange {
55        /// Source Rust type name.
56        from: &'static str,
57        /// Target storage type name.
58        to: &'static str,
59        /// Failing element index for vector conversions.
60        index: Option<usize>,
61    },
62    /// Value is non-finite (NaN or Inf) but target requires finite.
63    NonFinite {
64        /// Target storage type name.
65        to: &'static str,
66        /// Failing element index for vector conversions.
67        index: Option<usize>,
68    },
69    /// Conversion would lose precision.
70    PrecisionLoss {
71        /// Target storage type name.
72        to: &'static str,
73        /// Failing element index for vector conversions.
74        index: Option<usize>,
75    },
76    /// Float value is not integral but target is integer type.
77    NotIntegral {
78        /// Target storage type name.
79        to: &'static str,
80        /// Failing element index for vector conversions.
81        index: Option<usize>,
82    },
83    /// Missing value (NA) cannot be represented in target type.
84    MissingValue {
85        /// Target storage type name.
86        to: &'static str,
87        /// Failing element index for vector conversions.
88        index: Option<usize>,
89    },
90    /// Invalid UTF-8 in string conversion.
91    InvalidUtf8 {
92        /// Failing element index for vector conversions.
93        index: Option<usize>,
94    },
95}
96
97impl fmt::Display for StorageCoerceError {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        match self {
100            StorageCoerceError::Unsupported { from, to } => {
101                write!(f, "cannot convert {} to {}", from, to)
102            }
103            StorageCoerceError::OutOfRange { from, to, index } => {
104                if let Some(i) = index {
105                    write!(f, "value at index {} out of range for {} → {}", i, from, to)
106                } else {
107                    write!(f, "value out of range for {} → {}", from, to)
108                }
109            }
110            StorageCoerceError::NonFinite { to, index } => {
111                if let Some(i) = index {
112                    write!(
113                        f,
114                        "non-finite value at index {} cannot convert to {}",
115                        i, to
116                    )
117                } else {
118                    write!(f, "non-finite value cannot convert to {}", to)
119                }
120            }
121            StorageCoerceError::PrecisionLoss { to, index } => {
122                if let Some(i) = index {
123                    write!(
124                        f,
125                        "value at index {} would lose precision converting to {}",
126                        i, to
127                    )
128                } else {
129                    write!(f, "value would lose precision converting to {}", to)
130                }
131            }
132            StorageCoerceError::NotIntegral { to, index } => {
133                if let Some(i) = index {
134                    write!(
135                        f,
136                        "non-integral value at index {} cannot convert to {}",
137                        i, to
138                    )
139                } else {
140                    write!(f, "non-integral value cannot convert to {}", to)
141                }
142            }
143            StorageCoerceError::MissingValue { to, index } => {
144                if let Some(i) = index {
145                    write!(f, "missing value at index {} cannot convert to {}", i, to)
146                } else {
147                    write!(f, "missing value cannot convert to {}", to)
148                }
149            }
150            StorageCoerceError::InvalidUtf8 { index } => {
151                if let Some(i) = index {
152                    write!(f, "invalid UTF-8 at index {}", i)
153                } else {
154                    write!(f, "invalid UTF-8")
155                }
156            }
157        }
158    }
159}
160
161impl std::error::Error for StorageCoerceError {}
162
163impl StorageCoerceError {
164    /// Add index information to the error.
165    #[inline]
166    pub fn at_index(self, idx: usize) -> Self {
167        match self {
168            StorageCoerceError::OutOfRange { from, to, .. } => StorageCoerceError::OutOfRange {
169                from,
170                to,
171                index: Some(idx),
172            },
173            StorageCoerceError::NonFinite { to, .. } => StorageCoerceError::NonFinite {
174                to,
175                index: Some(idx),
176            },
177            StorageCoerceError::PrecisionLoss { to, .. } => StorageCoerceError::PrecisionLoss {
178                to,
179                index: Some(idx),
180            },
181            StorageCoerceError::NotIntegral { to, .. } => StorageCoerceError::NotIntegral {
182                to,
183                index: Some(idx),
184            },
185            StorageCoerceError::MissingValue { to, .. } => StorageCoerceError::MissingValue {
186                to,
187                index: Some(idx),
188            },
189            StorageCoerceError::InvalidUtf8 { .. } => {
190                StorageCoerceError::InvalidUtf8 { index: Some(idx) }
191            }
192            other => other,
193        }
194    }
195}
196// endregion
197
198// region: Trait definition
199
200/// Storage-directed conversion to R SEXP.
201///
202/// This trait allows converting Rust values to R with an explicit target storage
203/// type. The conversion is value-based: it succeeds if all values fit the target
204/// type, and fails otherwise.
205///
206/// # Target Types
207///
208/// - `i32` → R integer (INTSXP)
209/// - `f64` → R numeric (REALSXP)
210/// - `RLogical` → R logical (LGLSXP)
211/// - `u8` → R raw (RAWSXP)
212/// - `String` → R character (STRSXP)
213///
214/// # Example
215///
216/// ```ignore
217/// use miniextendr_api::IntoRAs;
218///
219/// // Convert i64 to R integer (if values fit)
220/// let x: Vec<i64> = vec![1, 2, 3];
221/// let sexp = x.into_r_as::<i32>()?;
222///
223/// // Convert f64 to R integer (if values are integral)
224/// let y: Vec<f64> = vec![1.0, 2.0, 3.0];
225/// let sexp = y.into_r_as::<i32>()?;
226/// ```
227pub trait IntoRAs<Target> {
228    /// Convert to R SEXP with the specified target storage type.
229    fn into_r_as(self) -> Result<SEXP, StorageCoerceError>;
230}
231// endregion
232
233// region: Helper: try_coerce_scalar with error mapping
234
235/// Try to coerce a scalar value, mapping CoerceError to StorageCoerceError.
236#[inline]
237fn try_coerce_scalar<T, R>(
238    value: T,
239    from: &'static str,
240    to: &'static str,
241) -> Result<R, StorageCoerceError>
242where
243    T: TryCoerce<R>,
244    T::Error: Into<CoerceErrorKind>,
245{
246    value
247        .try_coerce()
248        .map_err(|e| map_coerce_error(e.into(), from, to))
249}
250
251/// Internal enum to unify different coerce error types.
252#[derive(Debug)]
253enum CoerceErrorKind {
254    Overflow,
255    PrecisionLoss,
256    NaN,
257    Infallible,
258}
259
260impl From<CoerceError> for CoerceErrorKind {
261    fn from(e: CoerceError) -> Self {
262        match e {
263            CoerceError::Overflow => CoerceErrorKind::Overflow,
264            CoerceError::PrecisionLoss => CoerceErrorKind::PrecisionLoss,
265            CoerceError::NaN => CoerceErrorKind::NaN,
266            CoerceError::Zero => CoerceErrorKind::Overflow, // Treat zero error as overflow
267        }
268    }
269}
270
271impl From<std::convert::Infallible> for CoerceErrorKind {
272    fn from(_: std::convert::Infallible) -> Self {
273        CoerceErrorKind::Infallible
274    }
275}
276
277fn map_coerce_error(
278    kind: CoerceErrorKind,
279    from: &'static str,
280    to: &'static str,
281) -> StorageCoerceError {
282    match kind {
283        CoerceErrorKind::Overflow => StorageCoerceError::OutOfRange {
284            from,
285            to,
286            index: None,
287        },
288        CoerceErrorKind::PrecisionLoss => StorageCoerceError::PrecisionLoss { to, index: None },
289        CoerceErrorKind::NaN => StorageCoerceError::NonFinite { to, index: None },
290        CoerceErrorKind::Infallible => unreachable!(),
291    }
292}
293// endregion
294
295// region: Scalar implementations: -> i32 (R integer)
296
297macro_rules! impl_into_r_as_i32_scalar {
298    ($from:ty, $from_name:literal) => {
299        impl IntoRAs<i32> for $from {
300            #[inline]
301            fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
302                let v: i32 = try_coerce_scalar(self, $from_name, "i32")?;
303                Ok(v.into_sexp())
304            }
305        }
306    };
307}
308
309// Identity
310impl IntoRAs<i32> for i32 {
311    #[inline]
312    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
313        Ok(self.into_sexp())
314    }
315}
316
317// Widening (infallible)
318impl_into_r_as_i32_scalar!(i8, "i8");
319impl_into_r_as_i32_scalar!(i16, "i16");
320impl_into_r_as_i32_scalar!(u8, "u8");
321impl_into_r_as_i32_scalar!(u16, "u16");
322
323// Narrowing (fallible)
324impl_into_r_as_i32_scalar!(i64, "i64");
325impl_into_r_as_i32_scalar!(isize, "isize");
326impl_into_r_as_i32_scalar!(u32, "u32");
327impl_into_r_as_i32_scalar!(u64, "u64");
328impl_into_r_as_i32_scalar!(usize, "usize");
329
330// Float to int (fallible - must be integral and in range)
331impl_into_r_as_i32_scalar!(f32, "f32");
332impl_into_r_as_i32_scalar!(f64, "f64");
333
334// Bool to int
335impl IntoRAs<i32> for bool {
336    #[inline]
337    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
338        Ok((self as i32).into_sexp())
339    }
340}
341// endregion
342
343// region: Scalar implementations: -> f64 (R numeric)
344
345macro_rules! impl_into_r_as_f64_scalar {
346    ($from:ty, $from_name:literal) => {
347        impl IntoRAs<f64> for $from {
348            #[inline]
349            fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
350                let v: f64 = try_coerce_scalar(self, $from_name, "f64")?;
351                Ok(v.into_sexp())
352            }
353        }
354    };
355}
356
357// Identity
358impl IntoRAs<f64> for f64 {
359    #[inline]
360    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
361        if !self.is_finite() {
362            return Err(StorageCoerceError::NonFinite {
363                to: "f64",
364                index: None,
365            });
366        }
367        Ok(self.into_sexp())
368    }
369}
370
371// Widening from f32 (check finite)
372impl IntoRAs<f64> for f32 {
373    #[inline]
374    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
375        if !self.is_finite() {
376            return Err(StorageCoerceError::NonFinite {
377                to: "f64",
378                index: None,
379            });
380        }
381        Ok((self as f64).into_sexp())
382    }
383}
384
385// Widening from integers (infallible for small types)
386impl IntoRAs<f64> for i8 {
387    #[inline]
388    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
389        Ok((self as f64).into_sexp())
390    }
391}
392
393impl IntoRAs<f64> for i16 {
394    #[inline]
395    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
396        Ok((self as f64).into_sexp())
397    }
398}
399
400impl IntoRAs<f64> for i32 {
401    #[inline]
402    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
403        Ok((self as f64).into_sexp())
404    }
405}
406
407impl IntoRAs<f64> for u8 {
408    #[inline]
409    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
410        Ok((self as f64).into_sexp())
411    }
412}
413
414impl IntoRAs<f64> for u16 {
415    #[inline]
416    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
417        Ok((self as f64).into_sexp())
418    }
419}
420
421impl IntoRAs<f64> for u32 {
422    #[inline]
423    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
424        Ok((self as f64).into_sexp())
425    }
426}
427
428// Large integers: check precision (> 2^53 loses precision)
429impl_into_r_as_f64_scalar!(i64, "i64");
430impl_into_r_as_f64_scalar!(u64, "u64");
431impl_into_r_as_f64_scalar!(isize, "isize");
432impl_into_r_as_f64_scalar!(usize, "usize");
433
434// Bool to f64
435impl IntoRAs<f64> for bool {
436    #[inline]
437    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
438        Ok((if self { 1.0 } else { 0.0 }).into_sexp())
439    }
440}
441// endregion
442
443// region: Scalar implementations: -> u8 (R raw)
444
445macro_rules! impl_into_r_as_u8_scalar {
446    ($from:ty, $from_name:literal) => {
447        impl IntoRAs<u8> for $from {
448            #[inline]
449            fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
450                let v: u8 = try_coerce_scalar(self, $from_name, "u8")?;
451                Ok(v.into_sexp())
452            }
453        }
454    };
455}
456
457// Identity
458impl IntoRAs<u8> for u8 {
459    #[inline]
460    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
461        Ok(self.into_sexp())
462    }
463}
464
465// Narrowing (fallible)
466impl_into_r_as_u8_scalar!(i8, "i8");
467impl_into_r_as_u8_scalar!(i16, "i16");
468impl_into_r_as_u8_scalar!(i32, "i32");
469impl_into_r_as_u8_scalar!(i64, "i64");
470impl_into_r_as_u8_scalar!(isize, "isize");
471impl_into_r_as_u8_scalar!(u16, "u16");
472impl_into_r_as_u8_scalar!(u32, "u32");
473impl_into_r_as_u8_scalar!(u64, "u64");
474impl_into_r_as_u8_scalar!(usize, "usize");
475impl_into_r_as_u8_scalar!(f32, "f32");
476impl_into_r_as_u8_scalar!(f64, "f64");
477// endregion
478
479// region: Scalar implementations: -> RLogical (R logical)
480
481impl IntoRAs<RLogical> for bool {
482    #[inline]
483    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
484        Ok(self.into_sexp())
485    }
486}
487
488impl IntoRAs<RLogical> for RLogical {
489    #[inline]
490    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
491        Ok(self.into_sexp())
492    }
493}
494
495// Integer to logical: only 0, 1, NA_INTEGER allowed
496impl IntoRAs<RLogical> for i32 {
497    #[inline]
498    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
499        match self {
500            0 => Ok(false.into_sexp()),
501            1 => Ok(true.into_sexp()),
502            crate::altrep_traits::NA_INTEGER => Ok(RLogical::NA.into_sexp()),
503            _ => Err(StorageCoerceError::OutOfRange {
504                from: "i32",
505                to: "RLogical",
506                index: None,
507            }),
508        }
509    }
510}
511// endregion
512
513// region: Scalar implementations: -> String (R character)
514
515impl IntoRAs<String> for String {
516    #[inline]
517    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
518        Ok(self.into_sexp())
519    }
520}
521
522impl IntoRAs<String> for &str {
523    #[inline]
524    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
525        Ok(self.into_sexp())
526    }
527}
528
529// Numeric to String: stringify (including NaN/Inf)
530impl IntoRAs<String> for f64 {
531    #[inline]
532    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
533        let s = if self.is_nan() {
534            "NaN".to_string()
535        } else if self.is_infinite() {
536            if self.is_sign_positive() {
537                "Inf".to_string()
538            } else {
539                "-Inf".to_string()
540            }
541        } else {
542            self.to_string()
543        };
544        Ok(s.into_sexp())
545    }
546}
547
548impl IntoRAs<String> for f32 {
549    #[inline]
550    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
551        <f64 as IntoRAs<String>>::into_r_as(self as f64)
552    }
553}
554
555impl IntoRAs<String> for i32 {
556    #[inline]
557    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
558        Ok(self.to_string().into_sexp())
559    }
560}
561
562impl IntoRAs<String> for i64 {
563    #[inline]
564    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
565        Ok(self.to_string().into_sexp())
566    }
567}
568
569impl IntoRAs<String> for bool {
570    #[inline]
571    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
572        Ok((if self { "TRUE" } else { "FALSE" }).into_sexp())
573    }
574}
575
576impl IntoRAs<String> for RLogical {
577    #[inline]
578    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
579        let s = match self.to_option_bool() {
580            None => "NA",
581            Some(true) => "TRUE",
582            Some(false) => "FALSE",
583        };
584        Ok(s.into_sexp())
585    }
586}
587// endregion
588
589// region: Vec implementations: -> i32 (R integer vector)
590
591macro_rules! impl_vec_into_r_as_i32 {
592    ($from:ty, $from_name:literal) => {
593        impl IntoRAs<i32> for Vec<$from> {
594            fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
595                let mut result = Vec::with_capacity(self.len());
596                for (i, val) in self.into_iter().enumerate() {
597                    let v: i32 =
598                        try_coerce_scalar(val, $from_name, "i32").map_err(|e| e.at_index(i))?;
599                    result.push(v);
600                }
601                Ok(result.into_sexp())
602            }
603        }
604
605        impl IntoRAs<i32> for &[$from] {
606            fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
607                let mut result = Vec::with_capacity(self.len());
608                for (i, &val) in self.iter().enumerate() {
609                    let v: i32 =
610                        try_coerce_scalar(val, $from_name, "i32").map_err(|e| e.at_index(i))?;
611                    result.push(v);
612                }
613                Ok(result.into_sexp())
614            }
615        }
616    };
617}
618
619// Identity (direct copy)
620impl IntoRAs<i32> for Vec<i32> {
621    #[inline]
622    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
623        Ok(self.into_sexp())
624    }
625}
626
627impl IntoRAs<i32> for &[i32] {
628    #[inline]
629    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
630        Ok(self.into_sexp())
631    }
632}
633
634impl_vec_into_r_as_i32!(i8, "i8");
635impl_vec_into_r_as_i32!(i16, "i16");
636impl_vec_into_r_as_i32!(u8, "u8");
637impl_vec_into_r_as_i32!(u16, "u16");
638impl_vec_into_r_as_i32!(i64, "i64");
639impl_vec_into_r_as_i32!(isize, "isize");
640impl_vec_into_r_as_i32!(u32, "u32");
641impl_vec_into_r_as_i32!(u64, "u64");
642impl_vec_into_r_as_i32!(usize, "usize");
643impl_vec_into_r_as_i32!(f32, "f32");
644impl_vec_into_r_as_i32!(f64, "f64");
645
646// Vec<bool> -> i32
647impl IntoRAs<i32> for Vec<bool> {
648    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
649        let result: Vec<i32> = self.into_iter().map(|b| b as i32).collect();
650        Ok(result.into_sexp())
651    }
652}
653
654impl IntoRAs<i32> for &[bool] {
655    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
656        let result: Vec<i32> = self.iter().map(|&b| b as i32).collect();
657        Ok(result.into_sexp())
658    }
659}
660// endregion
661
662// region: Vec implementations: -> f64 (R numeric vector)
663
664macro_rules! impl_vec_into_r_as_f64 {
665    ($from:ty, $from_name:literal) => {
666        impl IntoRAs<f64> for Vec<$from> {
667            fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
668                let mut result = Vec::with_capacity(self.len());
669                for (i, val) in self.into_iter().enumerate() {
670                    let v: f64 =
671                        try_coerce_scalar(val, $from_name, "f64").map_err(|e| e.at_index(i))?;
672                    result.push(v);
673                }
674                Ok(result.into_sexp())
675            }
676        }
677
678        impl IntoRAs<f64> for &[$from] {
679            fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
680                let mut result = Vec::with_capacity(self.len());
681                for (i, &val) in self.iter().enumerate() {
682                    let v: f64 =
683                        try_coerce_scalar(val, $from_name, "f64").map_err(|e| e.at_index(i))?;
684                    result.push(v);
685                }
686                Ok(result.into_sexp())
687            }
688        }
689    };
690}
691
692// Identity - but check for finite values
693impl IntoRAs<f64> for Vec<f64> {
694    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
695        for (i, &val) in self.iter().enumerate() {
696            if !val.is_finite() {
697                return Err(StorageCoerceError::NonFinite {
698                    to: "f64",
699                    index: Some(i),
700                });
701            }
702        }
703        Ok(self.into_sexp())
704    }
705}
706
707impl IntoRAs<f64> for &[f64] {
708    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
709        for (i, &val) in self.iter().enumerate() {
710            if !val.is_finite() {
711                return Err(StorageCoerceError::NonFinite {
712                    to: "f64",
713                    index: Some(i),
714                });
715            }
716        }
717        Ok(self.into_sexp())
718    }
719}
720
721// f32 - check finite then widen
722impl IntoRAs<f64> for Vec<f32> {
723    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
724        let mut result = Vec::with_capacity(self.len());
725        for (i, val) in self.into_iter().enumerate() {
726            if !val.is_finite() {
727                return Err(StorageCoerceError::NonFinite {
728                    to: "f64",
729                    index: Some(i),
730                });
731            }
732            result.push(val as f64);
733        }
734        Ok(result.into_sexp())
735    }
736}
737
738impl IntoRAs<f64> for &[f32] {
739    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
740        let mut result = Vec::with_capacity(self.len());
741        for (i, &val) in self.iter().enumerate() {
742            if !val.is_finite() {
743                return Err(StorageCoerceError::NonFinite {
744                    to: "f64",
745                    index: Some(i),
746                });
747            }
748            result.push(val as f64);
749        }
750        Ok(result.into_sexp())
751    }
752}
753
754// Small integers - infallible widening
755macro_rules! impl_vec_into_r_as_f64_infallible {
756    ($from:ty) => {
757        impl IntoRAs<f64> for Vec<$from> {
758            fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
759                let result: Vec<f64> = self.into_iter().map(|v| v as f64).collect();
760                Ok(result.into_sexp())
761            }
762        }
763
764        impl IntoRAs<f64> for &[$from] {
765            fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
766                let result: Vec<f64> = self.iter().map(|&v| v as f64).collect();
767                Ok(result.into_sexp())
768            }
769        }
770    };
771}
772
773impl_vec_into_r_as_f64_infallible!(i8);
774impl_vec_into_r_as_f64_infallible!(i16);
775impl_vec_into_r_as_f64_infallible!(i32);
776impl_vec_into_r_as_f64_infallible!(u8);
777impl_vec_into_r_as_f64_infallible!(u16);
778impl_vec_into_r_as_f64_infallible!(u32);
779
780// Large integers - check precision
781impl_vec_into_r_as_f64!(i64, "i64");
782impl_vec_into_r_as_f64!(u64, "u64");
783impl_vec_into_r_as_f64!(isize, "isize");
784impl_vec_into_r_as_f64!(usize, "usize");
785
786// Vec<bool> -> f64
787impl IntoRAs<f64> for Vec<bool> {
788    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
789        let result: Vec<f64> = self
790            .into_iter()
791            .map(|b| if b { 1.0 } else { 0.0 })
792            .collect();
793        Ok(result.into_sexp())
794    }
795}
796
797impl IntoRAs<f64> for &[bool] {
798    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
799        let result: Vec<f64> = self.iter().map(|&b| if b { 1.0 } else { 0.0 }).collect();
800        Ok(result.into_sexp())
801    }
802}
803// endregion
804
805// region: Vec implementations: -> u8 (R raw vector)
806
807macro_rules! impl_vec_into_r_as_u8 {
808    ($from:ty, $from_name:literal) => {
809        impl IntoRAs<u8> for Vec<$from> {
810            fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
811                let mut result = Vec::with_capacity(self.len());
812                for (i, val) in self.into_iter().enumerate() {
813                    let v: u8 =
814                        try_coerce_scalar(val, $from_name, "u8").map_err(|e| e.at_index(i))?;
815                    result.push(v);
816                }
817                Ok(result.into_sexp())
818            }
819        }
820
821        impl IntoRAs<u8> for &[$from] {
822            fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
823                let mut result = Vec::with_capacity(self.len());
824                for (i, &val) in self.iter().enumerate() {
825                    let v: u8 =
826                        try_coerce_scalar(val, $from_name, "u8").map_err(|e| e.at_index(i))?;
827                    result.push(v);
828                }
829                Ok(result.into_sexp())
830            }
831        }
832    };
833}
834
835// Identity
836impl IntoRAs<u8> for Vec<u8> {
837    #[inline]
838    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
839        Ok(self.into_sexp())
840    }
841}
842
843impl IntoRAs<u8> for &[u8] {
844    #[inline]
845    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
846        Ok(self.into_sexp())
847    }
848}
849
850impl_vec_into_r_as_u8!(i8, "i8");
851impl_vec_into_r_as_u8!(i16, "i16");
852impl_vec_into_r_as_u8!(i32, "i32");
853impl_vec_into_r_as_u8!(i64, "i64");
854impl_vec_into_r_as_u8!(isize, "isize");
855impl_vec_into_r_as_u8!(u16, "u16");
856impl_vec_into_r_as_u8!(u32, "u32");
857impl_vec_into_r_as_u8!(u64, "u64");
858impl_vec_into_r_as_u8!(usize, "usize");
859impl_vec_into_r_as_u8!(f32, "f32");
860impl_vec_into_r_as_u8!(f64, "f64");
861// endregion
862
863// region: Vec implementations: -> RLogical (R logical vector)
864
865impl IntoRAs<RLogical> for Vec<bool> {
866    #[inline]
867    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
868        Ok(self.into_sexp())
869    }
870}
871
872impl IntoRAs<RLogical> for &[bool] {
873    #[inline]
874    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
875        Ok(self.into_sexp())
876    }
877}
878// endregion
879
880// region: Vec implementations: -> String (R character vector)
881
882impl IntoRAs<String> for Vec<String> {
883    #[inline]
884    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
885        Ok(self.into_sexp())
886    }
887}
888
889impl IntoRAs<String> for &[String] {
890    #[inline]
891    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
892        Ok(self.into_sexp())
893    }
894}
895
896impl IntoRAs<String> for Vec<&str> {
897    #[inline]
898    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
899        Ok(self.into_sexp())
900    }
901}
902
903impl IntoRAs<String> for &[&str] {
904    #[inline]
905    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
906        Ok(self.into_sexp())
907    }
908}
909
910// Numeric vectors to String (stringify)
911impl IntoRAs<String> for Vec<f64> {
912    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
913        let strings: Vec<String> = self
914            .into_iter()
915            .map(|v| {
916                if v.is_nan() {
917                    "NaN".to_string()
918                } else if v.is_infinite() {
919                    if v.is_sign_positive() {
920                        "Inf".to_string()
921                    } else {
922                        "-Inf".to_string()
923                    }
924                } else {
925                    v.to_string()
926                }
927            })
928            .collect();
929        Ok(strings.into_sexp())
930    }
931}
932
933impl IntoRAs<String> for Vec<i32> {
934    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
935        let strings: Vec<String> = self.into_iter().map(|v| v.to_string()).collect();
936        Ok(strings.into_sexp())
937    }
938}
939
940impl IntoRAs<String> for Vec<i64> {
941    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
942        let strings: Vec<String> = self.into_iter().map(|v| v.to_string()).collect();
943        Ok(strings.into_sexp())
944    }
945}
946
947impl IntoRAs<String> for Vec<bool> {
948    fn into_r_as(self) -> Result<SEXP, StorageCoerceError> {
949        let strings: Vec<String> = self
950            .into_iter()
951            .map(|b| if b { "TRUE" } else { "FALSE" }.to_string())
952            .collect();
953        Ok(strings.into_sexp())
954    }
955}
956// endregion
957
958// region: Tests
959
960#[cfg(test)]
961mod tests {
962    use super::*;
963
964    // Note: These are compile-time tests to ensure the trait is implemented correctly.
965    // Runtime tests require R to be initialized and should go in the integration tests.
966
967    fn _assert_into_r_as<T, Target>()
968    where
969        T: IntoRAs<Target>,
970    {
971    }
972
973    #[test]
974    fn test_trait_bounds() {
975        // Scalars -> i32
976        _assert_into_r_as::<i32, i32>();
977        _assert_into_r_as::<i64, i32>();
978        _assert_into_r_as::<f64, i32>();
979        _assert_into_r_as::<bool, i32>();
980
981        // Scalars -> f64
982        _assert_into_r_as::<f64, f64>();
983        _assert_into_r_as::<i32, f64>();
984        _assert_into_r_as::<i64, f64>();
985        _assert_into_r_as::<bool, f64>();
986
987        // Scalars -> u8
988        _assert_into_r_as::<u8, u8>();
989        _assert_into_r_as::<i32, u8>();
990
991        // Scalars -> RLogical
992        _assert_into_r_as::<bool, RLogical>();
993        _assert_into_r_as::<i32, RLogical>();
994
995        // Scalars -> String
996        _assert_into_r_as::<String, String>();
997        _assert_into_r_as::<&str, String>();
998        _assert_into_r_as::<f64, String>();
999        _assert_into_r_as::<i32, String>();
1000        _assert_into_r_as::<bool, String>();
1001
1002        // Vecs -> i32
1003        _assert_into_r_as::<Vec<i32>, i32>();
1004        _assert_into_r_as::<Vec<i64>, i32>();
1005        _assert_into_r_as::<Vec<f64>, i32>();
1006
1007        // Vecs -> f64
1008        _assert_into_r_as::<Vec<f64>, f64>();
1009        _assert_into_r_as::<Vec<i32>, f64>();
1010        _assert_into_r_as::<Vec<i64>, f64>();
1011
1012        // Vecs -> u8
1013        _assert_into_r_as::<Vec<u8>, u8>();
1014        _assert_into_r_as::<Vec<i32>, u8>();
1015
1016        // Vecs -> String
1017        _assert_into_r_as::<Vec<String>, String>();
1018        _assert_into_r_as::<Vec<f64>, String>();
1019    }
1020}
1021// endregion