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