Skip to main content

miniextendr_api/
convert.rs

1//! Wrapper helpers to force specific `IntoR` representations.
2//!
3//! This module provides two approaches for controlling how Rust types are converted to R:
4//!
5//! ## 1. `As*` Wrappers (Call-site Control)
6//!
7//! Use these wrappers when you want to override the conversion for a single return value:
8//!
9//! - [`AsList<T>`]: Convert `T` to an R list via [`IntoList`]
10//! - [`AsExternalPtr<T>`]: Convert `T` to an R external pointer
11//! - [`AsRNative<T>`]: Convert scalar `T` to a length-1 R vector
12//!
13//! ```ignore
14//! #[miniextendr]
15//! fn get_data() -> AsList<MyStruct> {
16//!     AsList(MyStruct { x: 1, y: 2 })
17//! }
18//! ```
19//!
20//! ## 2. `Prefer*` Derive Macros (Type-level Control)
21//!
22//! Use these derives when a type should *always* use a specific conversion:
23//!
24//! - `#[derive(IntoList, PreferList)]`: Type always converts to R list
25//! - `#[derive(ExternalPtr, PreferExternalPtr)]`: Type always converts to external pointer
26//! - `#[derive(RNativeType, PreferRNativeType)]`: Newtype always converts to native R scalar
27//!
28//! ```ignore
29//! #[derive(IntoList, PreferList)]
30//! struct Point { x: f64, y: f64 }
31//!
32//! #[miniextendr]
33//! fn make_point() -> Point {  // Automatically becomes R list
34//!     Point { x: 1.0, y: 2.0 }
35//! }
36//! ```
37//!
38//! ## 3. `#[miniextendr(return = "...")]` Attribute
39//!
40//! Use this when you want to control conversion for a specific `#[miniextendr]` function
41//! without modifying the type itself:
42//!
43//! - `return = "list"`: Wrap result in `AsList`
44//! - `return = "externalptr"`: Wrap result in `AsExternalPtr`
45//! - `return = "native"`: Wrap result in `AsRNative`
46//!
47//! ```ignore
48//! #[miniextendr(return = "list")]
49//! fn get_as_list() -> MyStruct {
50//!     MyStruct { x: 1 }
51//! }
52//! ```
53//!
54//! ## Choosing the Right Approach
55//!
56//! | Situation | Recommended Approach |
57//! |-----------|---------------------|
58//! | Type should *always* convert one way | `Prefer*` derive |
59//! | Override conversion for one function | `As*` wrapper or `return` attribute |
60//! | Type has multiple valid representations | Don't use `Prefer*`; use `As*` or `return` |
61
62use crate::externalptr::{ExternalPtr, IntoExternalPtr};
63use crate::ffi::{RNativeType, SexpExt};
64use crate::into_r::IntoR;
65use crate::list::{IntoList, List};
66use crate::named_vector::AtomicElement;
67
68/// Wrap a value and convert it to an R list via [`IntoList`] when returned from Rust.
69///
70/// Use this wrapper when you want to convert a single value to an R list without
71/// making that the default behavior for the type.
72///
73/// # Example
74///
75/// ```ignore
76/// #[derive(IntoList)]
77/// struct Point { x: f64, y: f64 }
78///
79/// #[miniextendr]
80/// fn make_point() -> AsList<Point> {
81///     AsList(Point { x: 1.0, y: 2.0 })
82/// }
83/// // In R: make_point() returns list(x = 1.0, y = 2.0)
84/// ```
85#[derive(Debug, Clone, Copy)]
86pub struct AsList<T: IntoList>(pub T);
87
88impl<T: IntoList> From<T> for AsList<T> {
89    fn from(value: T) -> Self {
90        AsList(value)
91    }
92}
93
94impl<T: IntoList> IntoR for AsList<T> {
95    type Error = std::convert::Infallible;
96
97    #[inline]
98    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
99        Ok(self.into_sexp())
100    }
101
102    #[inline]
103    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
104        self.try_into_sexp()
105    }
106
107    #[inline]
108    fn into_sexp(self) -> crate::ffi::SEXP {
109        self.0.into_list().into_sexp()
110    }
111}
112
113/// Wrap a value and convert it to an R data.frame via [`IntoDataFrame`] when returned from Rust.
114///
115/// Use this wrapper when you want to convert a single value to an R data.frame without
116/// making that the default behavior for the type.
117///
118/// # Example
119///
120/// ```ignore
121/// struct TimeSeries {
122///     timestamps: Vec<f64>,
123///     values: Vec<f64>,
124/// }
125///
126/// impl IntoDataFrame for TimeSeries {
127///     fn into_data_frame(self) -> List {
128///         List::from_pairs(vec![
129///             ("timestamp", self.timestamps),
130///             ("value", self.values),
131///         ])
132///         .set_class_str(&["data.frame"])
133///         .set_row_names_int(self.timestamps.len())
134///     }
135/// }
136///
137/// #[miniextendr]
138/// fn make_time_series() -> ToDataFrame<TimeSeries> {
139///     ToDataFrame(TimeSeries {
140///         timestamps: vec![1.0, 2.0, 3.0],
141///         values: vec![10.0, 20.0, 30.0],
142///     })
143/// }
144/// // In R: make_time_series() returns a data.frame
145/// ```
146#[derive(Debug, Clone, Copy)]
147pub struct ToDataFrame<T: IntoDataFrame>(pub T);
148
149impl<T: IntoDataFrame> From<T> for ToDataFrame<T> {
150    fn from(value: T) -> Self {
151        ToDataFrame(value)
152    }
153}
154
155impl<T: IntoDataFrame> IntoR for ToDataFrame<T> {
156    type Error = std::convert::Infallible;
157
158    #[inline]
159    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
160        Ok(self.into_sexp())
161    }
162
163    #[inline]
164    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
165        self.try_into_sexp()
166    }
167
168    #[inline]
169    fn into_sexp(self) -> crate::ffi::SEXP {
170        self.0.into_data_frame().into_sexp()
171    }
172}
173
174/// IntoR implementation for DataFrame.
175///
176/// This allows DataFrame to be returned directly from `#[miniextendr]` functions.
177impl<T: IntoList> IntoR for DataFrame<T> {
178    type Error = std::convert::Infallible;
179
180    #[inline]
181    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
182        Ok(self.into_sexp())
183    }
184
185    #[inline]
186    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
187        self.try_into_sexp()
188    }
189
190    #[inline]
191    fn into_sexp(self) -> crate::ffi::SEXP {
192        self.into_data_frame().into_sexp()
193    }
194}
195
196/// Trait for types that can be converted into R data frames.
197///
198/// This trait allows Rust types to define how they convert to R data frames.
199/// Use with [`ToDataFrame`] wrapper or `#[derive(PreferDataFrame)]` to enable
200/// automatic conversion.
201///
202/// # Example
203///
204/// ```ignore
205/// use miniextendr_api::convert::IntoDataFrame;
206/// use miniextendr_api::List;
207///
208/// struct TimeSeries {
209///     timestamps: Vec<f64>,
210///     values: Vec<f64>,
211/// }
212///
213/// impl IntoDataFrame for TimeSeries {
214///     fn into_data_frame(self) -> List {
215///         List::from_pairs(vec![
216///             ("timestamp", self.timestamps),
217///             ("value", self.values),
218///         ])
219///         .set_class_str(&["data.frame"])
220///         .set_row_names_int(self.timestamps.len())
221///     }
222/// }
223/// ```
224///
225/// # Comparison with `AsDataFrame` coercion trait
226///
227/// - [`AsDataFrame`](crate::as_coerce::AsDataFrame): Used with `#[miniextendr(as = "data.frame")]`
228///   to generate S3 methods for `as.data.frame()` on external pointer types
229/// - `IntoDataFrame`: Used for direct conversion when returning from functions
230///
231/// Both return a `List` with appropriate data.frame attributes, but serve different purposes:
232/// - S3 `AsDataFrame` is for coercion methods on existing objects (`&self`)
233/// - `IntoDataFrame` is for consuming conversion (`self`) when returning from functions
234pub trait IntoDataFrame {
235    /// Convert this value into an R data.frame.
236    ///
237    /// The returned List should have:
238    /// - Named columns of equal length
239    /// - Class attribute set to "data.frame"
240    /// - row.names attribute set appropriately
241    ///
242    /// # Example
243    ///
244    /// ```ignore
245    /// impl IntoDataFrame for MyStruct {
246    ///     fn into_data_frame(self) -> List {
247    ///         List::from_pairs(vec![
248    ///             ("col1", self.field1),
249    ///             ("col2", self.field2),
250    ///         ])
251    ///         .set_class_str(&["data.frame"])
252    ///         .set_row_names_int(self.field1.len())
253    ///     }
254    /// }
255    /// ```
256    fn into_data_frame(self) -> List;
257
258    /// Extract named column SEXPs from this DataFrame.
259    ///
260    /// Returns a `Vec<(String, SEXP)>` where each entry is a column name and
261    /// the raw SEXP for that column. The SEXPs are owned by the data frame SEXP
262    /// and **must** be protected by the caller before the data frame SEXP is
263    /// released.
264    ///
265    /// Used by `DataFrameRow`-derived enum code to flatten struct-typed fields.
266    /// The default implementation calls `into_data_frame()` and extracts the names
267    /// and elements from the resulting VECSXP. Override only if you need a more
268    /// efficient extraction path.
269    ///
270    /// # Safety
271    ///
272    /// This method calls R API functions and must run on the R main thread.
273    #[doc(hidden)]
274    fn into_named_columns(self) -> Vec<(String, crate::ffi::SEXP)>
275    where
276        Self: Sized,
277    {
278        use crate::ffi::SexpExt as _;
279        let list = self.into_data_frame();
280        let sexp = list.as_sexp();
281        let n = sexp.len();
282        let mut out = Vec::with_capacity(n);
283        let names_sexp = sexp.get_names();
284        let has_names = !names_sexp.is_nil();
285        for i in 0..(n as isize) {
286            let col_sexp = sexp.vector_elt(i);
287            let col_name = if has_names {
288                names_sexp.string_elt_str(i).unwrap_or("").to_string()
289            } else {
290                i.to_string()
291            };
292            out.push((col_name, col_sexp));
293        }
294        out
295    }
296}
297
298// region: Struct-field scatter helper
299
300/// Scatter a typed column SEXP from a dense inner data frame into a new
301/// SEXP of length `n_rows`, placing `NA`/`NULL` at rows not in `present_idx`.
302///
303/// This is called by `DataFrameRow`-derived enum code to flatten struct-typed
304/// variant fields into prefixed columns of the parent data frame.
305///
306/// The output type mirrors the input:
307/// - REALSXP → REALSXP (NA_real_ fill)
308/// - INTSXP  → INTSXP  (NA_integer_ fill)
309/// - LGLSXP  → LGLSXP  (NA_logical fill)
310/// - STRSXP  → STRSXP  (NA_character_ fill)
311/// - VECSXP  → VECSXP  (R_NilValue fill)
312/// - anything else → VECSXP (R_NilValue fill)
313///
314/// # Safety
315///
316/// Must be called on the R main thread. `src` must be a valid SEXP of length
317/// `>= present_idx.len()`. `n_rows` must equal the total row count of the
318/// parent data frame.
319#[doc(hidden)]
320pub unsafe fn scatter_column(
321    src: crate::ffi::SEXP,
322    present_idx: &[usize],
323    n_rows: usize,
324) -> crate::ffi::SEXP {
325    // SAFETY: caller guarantees R main thread; src is valid; n_rows is correct.
326    #[allow(unused_unsafe)]
327    unsafe {
328        use crate::ffi::{SEXPTYPE, SexpExt as _};
329
330        let stype = src.type_of();
331        let n_present = present_idx.len();
332
333        match stype {
334            SEXPTYPE::REALSXP => {
335                let out = crate::ffi::Rf_allocVector(SEXPTYPE::REALSXP, n_rows as isize);
336                // Fill with NA_real_ (R's NA for doubles is a specific NaN bit pattern;
337                // f64::NAN has the correct bit pattern since R uses a quiet NaN sentinel).
338                for i in 0..(n_rows as isize) {
339                    out.set_real_elt(i, f64::NAN);
340                }
341                for (pi, &row_i) in present_idx.iter().enumerate() {
342                    if pi < n_present {
343                        out.set_real_elt(row_i as isize, src.real_elt(pi as isize));
344                    }
345                }
346                out
347            }
348            SEXPTYPE::INTSXP => {
349                let out = crate::ffi::Rf_allocVector(SEXPTYPE::INTSXP, n_rows as isize);
350                for i in 0..(n_rows as isize) {
351                    out.set_integer_elt(i, i32::MIN); // NA_integer_
352                }
353                for (pi, &row_i) in present_idx.iter().enumerate() {
354                    if pi < n_present {
355                        out.set_integer_elt(row_i as isize, src.integer_elt(pi as isize));
356                    }
357                }
358                out
359            }
360            SEXPTYPE::LGLSXP => {
361                let out = crate::ffi::Rf_allocVector(SEXPTYPE::LGLSXP, n_rows as isize);
362                for i in 0..(n_rows as isize) {
363                    out.set_logical_elt(i, i32::MIN); // NA_logical
364                }
365                for (pi, &row_i) in present_idx.iter().enumerate() {
366                    if pi < n_present {
367                        out.set_logical_elt(row_i as isize, src.logical_elt(pi as isize));
368                    }
369                }
370                out
371            }
372            SEXPTYPE::STRSXP => {
373                let out = crate::ffi::Rf_allocVector(SEXPTYPE::STRSXP, n_rows as isize);
374                // Fill with NA_character_
375                for i in 0..(n_rows as isize) {
376                    out.set_string_elt(i, crate::ffi::SEXP::na_string());
377                }
378                for (pi, &row_i) in present_idx.iter().enumerate() {
379                    if pi < n_present {
380                        let charsxp = src.string_elt(pi as isize);
381                        out.set_string_elt(row_i as isize, charsxp);
382                    }
383                }
384                out
385            }
386            SEXPTYPE::VECSXP => {
387                let out = crate::ffi::Rf_allocVector(SEXPTYPE::VECSXP, n_rows as isize);
388                // R_NilValue fill is automatic (Rf_allocVector zero-initialises VECSXP slots).
389                for (pi, &row_i) in present_idx.iter().enumerate() {
390                    if pi < n_present {
391                        let elt = src.vector_elt(pi as isize);
392                        out.set_vector_elt(row_i as isize, elt);
393                    }
394                }
395                out
396            }
397            _ => {
398                // Unknown/unsupported type — produce a VECSXP list-column.
399                // Cells for absent rows remain R_NilValue.
400                let out = crate::ffi::Rf_allocVector(SEXPTYPE::VECSXP, n_rows as isize);
401                for (pi, &row_i) in present_idx.iter().enumerate() {
402                    if pi < n_present {
403                        let elt = src.vector_elt(pi as isize);
404                        out.set_vector_elt(row_i as isize, elt);
405                    }
406                }
407                out
408            }
409        }
410    }
411}
412// endregion
413
414// region: Serde Row Wrapper
415
416/// Wrap a serde-serializable value for use as a data frame row.
417///
418/// This wrapper implements [`IntoList`] via serde serialization, allowing
419/// types that implement `serde::Serialize` to be used with [`DataFrame`]
420/// without manually implementing [`IntoList`].
421///
422/// # Feature Flag
423///
424/// Requires the `serde` feature to be enabled.
425///
426/// # Example
427///
428/// ```ignore
429/// use miniextendr_api::{miniextendr, convert::{AsSerializeRow, DataFrame}};
430/// use serde::Serialize;
431///
432/// #[derive(Serialize)]
433/// struct Measurement {
434///     time: f64,
435///     value: f64,
436/// }
437///
438/// #[miniextendr]
439/// fn get_data() -> DataFrame<AsSerializeRow<Measurement>> {
440///     DataFrame::from_rows(vec![
441///         AsSerializeRow(Measurement { time: 1.0, value: 10.0 }),
442///         AsSerializeRow(Measurement { time: 2.0, value: 20.0 }),
443///     ])
444/// }
445/// ```
446#[cfg(feature = "serde")]
447#[derive(Debug, Clone, Copy)]
448pub struct AsSerializeRow<T: serde::Serialize>(pub T);
449
450#[cfg(feature = "serde")]
451impl<T: serde::Serialize> From<T> for AsSerializeRow<T> {
452    fn from(value: T) -> Self {
453        AsSerializeRow(value)
454    }
455}
456
457#[cfg(feature = "serde")]
458impl<T: serde::Serialize> IntoList for AsSerializeRow<T> {
459    fn into_list(self) -> List {
460        use crate::ffi::{SEXPTYPE, SexpExt};
461        use crate::serde::RSerializer;
462        match RSerializer::to_sexp(&self.0) {
463            Ok(sexp) => {
464                if sexp.type_of() == SEXPTYPE::VECSXP {
465                    unsafe { List::from_raw(sexp) }
466                } else {
467                    // Non-list SEXP (e.g., scalar) — wrap in a single-element list
468                    List::from_raw_values(vec![sexp])
469                }
470            }
471            Err(e) => {
472                panic!("AsSerializeRow: serde serialization failed: {e}");
473            }
474        }
475    }
476}
477
478/// Type alias for a [`DataFrame`] of serde-serializable rows.
479///
480/// This is equivalent to `DataFrame<AsSerializeRow<T>>` but more concise.
481///
482/// # Example
483///
484/// ```ignore
485/// use miniextendr_api::{miniextendr, SerializeDataFrame};
486/// use serde::Serialize;
487///
488/// #[derive(Serialize)]
489/// struct Person {
490///     name: String,
491///     age: i32,
492/// }
493///
494/// #[miniextendr]
495/// fn get_people() -> SerializeDataFrame<Person> {
496///     let people = vec![
497///         Person { name: "Alice".into(), age: 30 },
498///         Person { name: "Bob".into(), age: 25 },
499///     ];
500///     SerializeDataFrame::from_serialize(people)
501/// }
502/// ```
503#[cfg(feature = "serde")]
504pub type SerializeDataFrame<T> = DataFrame<AsSerializeRow<T>>;
505// endregion
506
507// region: Data Frame Row Conversion
508
509/// Convert row-oriented data into a column-oriented R data.frame.
510///
511/// This type collects a sequence of row elements (structs implementing [`IntoList`])
512/// and transposes them into column vectors suitable for creating an R data.frame.
513///
514/// # Example
515///
516/// ```ignore
517/// use miniextendr_api::{miniextendr, convert::DataFrame};
518///
519/// #[derive(IntoList)]
520/// struct Person {
521///     name: String,
522///     age: i32,
523///     height: f64,
524/// }
525///
526/// #[miniextendr]
527/// fn make_people() -> DataFrame<Person> {
528///     DataFrame::from_rows(vec![
529///         Person { name: "Alice".into(), age: 30, height: 165.0 },
530///         Person { name: "Bob".into(), age: 25, height: 180.0 },
531///         Person { name: "Carol".into(), age: 35, height: 170.0 },
532///     ])
533/// }
534/// // In R: make_people() returns a data.frame with 3 rows and columns: name, age, height
535/// ```
536///
537/// # Row-oriented to Column-oriented
538///
539/// R data frames are column-oriented (each column is a vector), but data is often
540/// produced row-by-row in Rust. `DataFrame` handles the transposition:
541///
542/// ```text
543/// Input (row-oriented):           Output (column-oriented):
544/// Row 1: {name: "A", age: 30}     name column:  ["A", "B", "C"]
545/// Row 2: {name: "B", age: 25}  →  age column:   [30, 25, 35]
546/// Row 3: {name: "C", age: 35}
547/// ```
548#[derive(Debug, Clone)]
549pub struct DataFrame<T: IntoList> {
550    rows: Vec<T>,
551}
552
553impl<T: IntoList> DataFrame<T> {
554    /// Create a new `DataFrame` from a vector of row elements.
555    pub fn from_rows(rows: Vec<T>) -> Self {
556        Self { rows }
557    }
558
559    /// Create an empty `DataFrame`.
560    pub fn new() -> Self {
561        Self { rows: Vec::new() }
562    }
563
564    /// Add a row to the data frame.
565    pub fn push(&mut self, row: T) {
566        self.rows.push(row);
567    }
568
569    /// Get the number of rows.
570    pub fn len(&self) -> usize {
571        self.rows.len()
572    }
573
574    /// Check if empty.
575    pub fn is_empty(&self) -> bool {
576        self.rows.is_empty()
577    }
578}
579
580#[cfg(feature = "serde")]
581impl<T: serde::Serialize> DataFrame<AsSerializeRow<T>> {
582    /// Create a DataFrame from serde-serializable rows.
583    ///
584    /// This is a convenience method that wraps each row in [`AsSerializeRow`]
585    /// automatically, avoiding the need to manually map over the input.
586    ///
587    /// # Example
588    ///
589    /// ```ignore
590    /// use miniextendr_api::DataFrame;
591    /// use serde::Serialize;
592    ///
593    /// #[derive(Serialize)]
594    /// struct Person { name: String, age: i32 }
595    ///
596    /// let people = vec![
597    ///     Person { name: "Alice".into(), age: 30 },
598    ///     Person { name: "Bob".into(), age: 25 },
599    /// ];
600    ///
601    /// // Instead of:
602    /// // DataFrame::from_iter(people.into_iter().map(AsSerializeRow))
603    ///
604    /// // Just write:
605    /// let df = DataFrame::from_serialize(people);
606    /// ```
607    pub fn from_serialize(rows: impl IntoIterator<Item = T>) -> Self {
608        Self::from_iter(rows.into_iter().map(AsSerializeRow))
609    }
610}
611
612impl<T: IntoList> Default for DataFrame<T> {
613    fn default() -> Self {
614        Self::new()
615    }
616}
617
618impl<T: IntoList> FromIterator<T> for DataFrame<T> {
619    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
620        Self {
621            rows: iter.into_iter().collect(),
622        }
623    }
624}
625
626impl<T: IntoList> IntoDataFrame for DataFrame<T> {
627    fn into_data_frame(self) -> List {
628        if self.rows.is_empty() {
629            // Empty data frame
630            return List::from_raw_pairs(Vec::<(&str, crate::ffi::SEXP)>::new())
631                .set_data_frame_class()
632                .set_row_names_int(0);
633        }
634
635        let mut n_protect: i32 = 0;
636
637        // Convert all rows to lists, protecting each from GC.
638        let lists: Vec<List> = self
639            .rows
640            .into_iter()
641            .map(|row| {
642                let list = row.into_list();
643                unsafe { crate::ffi::Rf_protect(list.as_sexp()) };
644                n_protect += 1;
645                list
646            })
647            .collect();
648        let n_rows = lists.len() as isize;
649
650        // Get column names from the first row
651        let first_names_sexp = lists[0].names();
652        if first_names_sexp.is_none() {
653            unsafe { crate::ffi::Rf_unprotect(n_protect) };
654            panic!("cannot create data frame from unnamed list elements");
655        }
656
657        // Extract column names as Vec<String>
658        let names_sexp = first_names_sexp.expect("checked is_none above");
659        let n_cols = names_sexp.xlength();
660        let mut col_names = Vec::with_capacity(n_cols as usize);
661        for i in 0..n_cols {
662            unsafe {
663                let name_sexp = names_sexp.string_elt(i);
664                let name_ptr = name_sexp.r_char();
665                let name_cstr = std::ffi::CStr::from_ptr(name_ptr);
666                if let Ok(s) = name_cstr.to_str() {
667                    col_names.push(s.to_string());
668                }
669            }
670        }
671
672        // Transpose: collect values by column.
673        // Element SEXPs from get_named are children of protected row lists,
674        // so they don't need individual protection.
675        use std::collections::HashMap;
676        let mut columns: HashMap<String, Vec<crate::ffi::SEXP>> =
677            HashMap::with_capacity(col_names.len());
678        for name in &col_names {
679            columns.insert(name.clone(), Vec::with_capacity(n_rows as usize));
680        }
681
682        for list in &lists {
683            for name in &col_names {
684                let value = list
685                    .get_named::<crate::ffi::SEXP>(name)
686                    .unwrap_or(crate::ffi::SEXP::nil());
687                columns
688                    .get_mut(name)
689                    .expect("column inserted above")
690                    .push(value);
691            }
692        }
693
694        // Build column vectors, protecting each from GC.
695        // Coalesce homogeneous length-1 scalars into atomic vectors so that
696        // columns are INTSXP/REALSXP/LGLSXP/STRSXP instead of VECSXP (list).
697        let mut df_pairs: Vec<(String, crate::ffi::SEXP)> = Vec::with_capacity(col_names.len());
698        for name in col_names {
699            let col_values = columns.remove(&name).expect("column inserted above");
700            let col_sexp = List::from_scalars_or_list(&col_values).as_sexp();
701            unsafe { crate::ffi::Rf_protect(col_sexp) };
702            n_protect += 1;
703            df_pairs.push((name, col_sexp));
704        }
705
706        let result = List::from_raw_pairs(df_pairs)
707            .set_data_frame_class()
708            .set_row_names_int(n_rows as usize);
709        unsafe { crate::ffi::Rf_unprotect(n_protect) };
710        result
711    }
712}
713
714/// Wrap a value and convert it to an R external pointer when returned from Rust.
715///
716/// Use this wrapper when you want to return a Rust value as an opaque pointer
717/// that R code can pass back to Rust functions later.
718///
719/// # Example
720///
721/// ```ignore
722/// struct Connection { handle: u64 }
723///
724/// impl IntoExternalPtr for Connection { /* ... */ }
725///
726/// #[miniextendr]
727/// fn open_connection(path: &str) -> AsExternalPtr<Connection> {
728///     AsExternalPtr(Connection { handle: 42 })
729/// }
730/// // In R: open_connection("foo") returns an external pointer
731/// ```
732#[derive(Debug, Clone, Copy)]
733pub struct AsExternalPtr<T: IntoExternalPtr>(pub T);
734
735impl<T: IntoExternalPtr> From<T> for AsExternalPtr<T> {
736    fn from(value: T) -> Self {
737        AsExternalPtr(value)
738    }
739}
740
741impl<T: IntoExternalPtr> IntoR for AsExternalPtr<T> {
742    type Error = std::convert::Infallible;
743
744    #[inline]
745    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
746        Ok(self.into_sexp())
747    }
748
749    #[inline]
750    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
751        self.try_into_sexp()
752    }
753
754    #[inline]
755    fn into_sexp(self) -> crate::ffi::SEXP {
756        ExternalPtr::new(self.0).into_sexp()
757    }
758}
759
760/// Wrap a scalar [`RNativeType`] and force native R vector conversion.
761///
762/// This creates a length-1 R vector containing the scalar value. Use this when
763/// you want to ensure a value is converted to its native R representation (e.g.,
764/// `i32` → integer vector, `f64` → numeric vector) rather than another path
765/// like `IntoExternalPtr`.
766///
767/// # Example
768///
769/// ```ignore
770/// #[derive(Clone, Copy, RNativeType)]
771/// struct Meters(f64);
772///
773/// #[miniextendr]
774/// fn distance() -> AsRNative<Meters> {
775///     AsRNative(Meters(42.5))
776/// }
777/// // In R: distance() returns 42.5 (numeric vector of length 1)
778/// ```
779///
780/// # Performance
781///
782/// This wrapper directly allocates an R vector and writes the value,
783/// avoiding intermediate Rust allocations.
784#[derive(Debug, Clone, Copy)]
785pub struct AsRNative<T: RNativeType>(pub T);
786
787impl<T: RNativeType> From<T> for AsRNative<T> {
788    fn from(value: T) -> Self {
789        AsRNative(value)
790    }
791}
792
793impl<T: RNativeType> IntoR for AsRNative<T> {
794    type Error = std::convert::Infallible;
795
796    #[inline]
797    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
798        Ok(self.into_sexp())
799    }
800
801    #[inline]
802    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
803        Ok(unsafe { self.into_sexp_unchecked() })
804    }
805
806    #[inline]
807    fn into_sexp(self) -> crate::ffi::SEXP {
808        // Directly allocate a length-1 R vector and write the scalar value.
809        // This avoids the intermediate Rust Vec allocation.
810        unsafe {
811            let sexp = crate::ffi::Rf_allocVector(T::SEXP_TYPE, 1);
812            let ptr = T::dataptr_mut(sexp);
813            std::ptr::write(ptr, self.0);
814            sexp
815        }
816    }
817
818    #[inline]
819    unsafe fn into_sexp_unchecked(self) -> crate::ffi::SEXP {
820        unsafe {
821            let sexp = crate::ffi::Rf_allocVector_unchecked(T::SEXP_TYPE, 1);
822            let ptr = T::dataptr_mut(sexp);
823            std::ptr::write(ptr, self.0);
824            sexp
825        }
826    }
827}
828// endregion
829
830// region: Named pair wrappers
831
832/// Wrap a tuple pair collection and convert it to a **named R list** (VECSXP).
833///
834/// Preserves insertion order and allows duplicate names (sequence semantics).
835///
836/// # Supported input types
837///
838/// | Input | Bounds |
839/// |-------|--------|
840/// | `Vec<(K, V)>` | `K: AsRef<str>`, `V: IntoR` |
841/// | `[(K, V); N]` | `K: AsRef<str>`, `V: IntoR` |
842/// | `&[(K, V)]` | `K: AsRef<str>`, `V: Clone + IntoR` |
843///
844/// # Example
845///
846/// ```ignore
847/// #[miniextendr]
848/// fn make_config() -> AsNamedList<Vec<(String, i32)>> {
849///     AsNamedList(vec![
850///         ("width".into(), 100),
851///         ("height".into(), 200),
852///     ])
853/// }
854/// // In R: make_config() returns list(width = 100L, height = 200L)
855/// ```
856#[derive(Debug, Clone)]
857pub struct AsNamedList<T>(pub T);
858
859impl<T> From<T> for AsNamedList<T> {
860    fn from(value: T) -> Self {
861        AsNamedList(value)
862    }
863}
864
865impl<K: AsRef<str>, V: IntoR> IntoR for AsNamedList<Vec<(K, V)>> {
866    type Error = std::convert::Infallible;
867
868    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
869        Ok(self.into_sexp())
870    }
871
872    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
873        self.try_into_sexp()
874    }
875
876    fn into_sexp(self) -> crate::ffi::SEXP {
877        let pairs: Vec<(K, crate::ffi::SEXP)> = self
878            .0
879            .into_iter()
880            .map(|(k, v)| (k, v.into_sexp()))
881            .collect();
882        List::from_raw_pairs(pairs).into_sexp()
883    }
884}
885
886impl<K: AsRef<str>, V: IntoR, const N: usize> IntoR for AsNamedList<[(K, V); N]> {
887    type Error = std::convert::Infallible;
888
889    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
890        Ok(self.into_sexp())
891    }
892
893    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
894        self.try_into_sexp()
895    }
896
897    fn into_sexp(self) -> crate::ffi::SEXP {
898        let pairs: Vec<(K, crate::ffi::SEXP)> = self
899            .0
900            .into_iter()
901            .map(|(k, v)| (k, v.into_sexp()))
902            .collect();
903        List::from_raw_pairs(pairs).into_sexp()
904    }
905}
906
907impl<K: AsRef<str>, V: Clone + IntoR> IntoR for AsNamedList<&[(K, V)]> {
908    type Error = std::convert::Infallible;
909
910    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
911        Ok(self.into_sexp())
912    }
913
914    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
915        self.try_into_sexp()
916    }
917
918    fn into_sexp(self) -> crate::ffi::SEXP {
919        let pairs: Vec<(&K, crate::ffi::SEXP)> = self
920            .0
921            .iter()
922            .map(|(k, v)| (k, v.clone().into_sexp()))
923            .collect();
924        List::from_raw_pairs(pairs).into_sexp()
925    }
926}
927
928/// Wrap a tuple pair collection and convert it to a **named atomic R vector**
929/// (INTSXP, REALSXP, LGLSXP, RAWSXP, or STRSXP).
930///
931/// Preserves insertion order and allows duplicate names (sequence semantics).
932/// Values must be homogeneous and implement [`AtomicElement`].
933///
934/// # Supported input types
935///
936/// | Input | Bounds |
937/// |-------|--------|
938/// | `Vec<(K, V)>` | `K: AsRef<str>`, `V: AtomicElement` |
939/// | `[(K, V); N]` | `K: AsRef<str>`, `V: AtomicElement` |
940/// | `&[(K, V)]` | `K: AsRef<str>`, `V: Clone + AtomicElement` |
941///
942/// # Example
943///
944/// ```ignore
945/// #[miniextendr]
946/// fn make_scores() -> AsNamedVector<Vec<(&str, f64)>> {
947///     AsNamedVector(vec![("alice", 95.0), ("bob", 87.5)])
948/// }
949/// // In R: make_scores() returns c(alice = 95.0, bob = 87.5)
950/// ```
951#[derive(Debug, Clone)]
952pub struct AsNamedVector<T>(pub T);
953
954impl<T> From<T> for AsNamedVector<T> {
955    fn from(value: T) -> Self {
956        AsNamedVector(value)
957    }
958}
959
960impl<K: AsRef<str>, V: AtomicElement> IntoR for AsNamedVector<Vec<(K, V)>> {
961    type Error = std::convert::Infallible;
962
963    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
964        Ok(self.into_sexp())
965    }
966
967    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
968        self.try_into_sexp()
969    }
970
971    fn into_sexp(self) -> crate::ffi::SEXP {
972        named_vector_from_pairs(self.0)
973    }
974}
975
976impl<K: AsRef<str>, V: AtomicElement, const N: usize> IntoR for AsNamedVector<[(K, V); N]> {
977    type Error = std::convert::Infallible;
978
979    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
980        Ok(self.into_sexp())
981    }
982
983    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
984        self.try_into_sexp()
985    }
986
987    fn into_sexp(self) -> crate::ffi::SEXP {
988        named_vector_from_pairs(self.0)
989    }
990}
991
992impl<K: AsRef<str>, V: Clone + AtomicElement> IntoR for AsNamedVector<&[(K, V)]> {
993    type Error = std::convert::Infallible;
994
995    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
996        Ok(self.into_sexp())
997    }
998
999    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1000        self.try_into_sexp()
1001    }
1002
1003    fn into_sexp(self) -> crate::ffi::SEXP {
1004        let (keys, values): (Vec<&K>, Vec<V>) = self.0.iter().map(|(k, v)| (k, v.clone())).unzip();
1005        let sexp = V::vec_to_sexp(values);
1006        unsafe {
1007            crate::ffi::Rf_protect(sexp);
1008            crate::named_vector::set_names_on_sexp(sexp, &keys);
1009            crate::ffi::Rf_unprotect(1);
1010        }
1011        sexp
1012    }
1013}
1014
1015/// Shared helper: build a named atomic vector from an owning iterator of (key, value) pairs.
1016fn named_vector_from_pairs<K, V>(pairs: impl IntoIterator<Item = (K, V)>) -> crate::ffi::SEXP
1017where
1018    K: AsRef<str>,
1019    V: AtomicElement,
1020{
1021    let (keys, values): (Vec<K>, Vec<V>) = pairs.into_iter().unzip();
1022    let sexp = V::vec_to_sexp(values);
1023    unsafe {
1024        crate::ffi::Rf_protect(sexp);
1025        crate::named_vector::set_names_on_sexp(sexp, &keys);
1026        crate::ffi::Rf_unprotect(1);
1027    }
1028    sexp
1029}
1030// endregion
1031
1032// region: Extension traits for ergonomic wrapping
1033//
1034// These extension traits provide method-style wrapping that works even when
1035// the destination type isn't constrained (i.e., `value.wrap_list()` instead
1036// of `value.into()` which requires type inference).
1037//
1038// ```ignore
1039// // These all work without type annotations:
1040// let wrapped = my_struct.wrap_list();
1041// let ptr = my_value.wrap_external_ptr();
1042// let native = my_num.wrap_r_native();
1043// ```
1044
1045/// Extension trait for wrapping values as [`AsList`].
1046///
1047/// This trait is automatically implemented for all types that implement [`IntoList`].
1048///
1049/// # Example
1050///
1051/// ```ignore
1052/// use miniextendr_api::convert::AsListExt;
1053///
1054/// #[derive(IntoList)]
1055/// struct Point { x: f64, y: f64 }
1056///
1057/// let point = Point { x: 1.0, y: 2.0 };
1058/// let wrapped: AsList<Point> = point.wrap_list();
1059/// ```
1060pub trait AsListExt: IntoList + Sized {
1061    /// Wrap `self` in [`AsList`] for R list conversion.
1062    fn wrap_list(self) -> AsList<Self> {
1063        AsList(self)
1064    }
1065}
1066
1067impl<T: IntoList> AsListExt for T {}
1068
1069/// Extension trait for wrapping values as [`ToDataFrame`].
1070///
1071/// This trait is automatically implemented for all types that implement [`IntoDataFrame`].
1072///
1073/// # Example
1074///
1075/// ```ignore
1076/// use miniextendr_api::convert::ToDataFrameExt;
1077///
1078/// struct TimeSeries {
1079///     timestamps: Vec<f64>,
1080///     values: Vec<f64>,
1081/// }
1082///
1083/// impl IntoDataFrame for TimeSeries {
1084///     fn into_data_frame(self) -> List {
1085///         List::from_pairs(vec![
1086///             ("timestamp", self.timestamps),
1087///             ("value", self.values),
1088///         ])
1089///         .set_class_str(&["data.frame"])
1090///         .set_row_names_int(self.timestamps.len())
1091///     }
1092/// }
1093///
1094/// let ts = TimeSeries { timestamps: vec![1.0, 2.0], values: vec![10.0, 20.0] };
1095/// let wrapped: ToDataFrame<TimeSeries> = ts.to_data_frame();
1096/// ```
1097pub trait ToDataFrameExt: IntoDataFrame + Sized {
1098    /// Wrap `self` in [`ToDataFrame`] for R data.frame conversion.
1099    fn to_data_frame(self) -> ToDataFrame<Self> {
1100        ToDataFrame(self)
1101    }
1102}
1103
1104impl<T: IntoDataFrame> ToDataFrameExt for T {}
1105
1106/// Extension trait for wrapping values as [`AsExternalPtr`].
1107///
1108/// This trait is automatically implemented for all types that implement [`IntoExternalPtr`].
1109///
1110/// # Example
1111///
1112/// ```ignore
1113/// use miniextendr_api::convert::AsExternalPtrExt;
1114///
1115/// #[derive(ExternalPtr)]
1116/// struct Connection { handle: u64 }
1117///
1118/// let conn = Connection { handle: 42 };
1119/// let wrapped: AsExternalPtr<Connection> = conn.wrap_external_ptr();
1120/// ```
1121pub trait AsExternalPtrExt: IntoExternalPtr + Sized {
1122    /// Wrap `self` in [`AsExternalPtr`] for R external pointer conversion.
1123    fn wrap_external_ptr(self) -> AsExternalPtr<Self> {
1124        AsExternalPtr(self)
1125    }
1126}
1127
1128impl<T: IntoExternalPtr> AsExternalPtrExt for T {}
1129
1130/// Extension trait for wrapping values as [`AsRNative`].
1131///
1132/// This trait is automatically implemented for all types that implement [`RNativeType`].
1133///
1134/// # Example
1135///
1136/// ```ignore
1137/// use miniextendr_api::convert::AsRNativeExt;
1138///
1139/// let x: f64 = 42.5;
1140/// let wrapped: AsRNative<f64> = x.wrap_r_native();
1141/// ```
1142pub trait AsRNativeExt: RNativeType + Sized {
1143    /// Wrap `self` in [`AsRNative`] for native R scalar conversion.
1144    fn wrap_r_native(self) -> AsRNative<Self> {
1145        AsRNative(self)
1146    }
1147}
1148
1149impl<T: RNativeType> AsRNativeExt for T {}
1150
1151/// Extension trait for wrapping tuple pair collections as [`AsNamedList`].
1152///
1153/// # Example
1154///
1155/// ```ignore
1156/// let pairs = vec![("x".to_string(), 1i32), ("y".to_string(), 2i32)];
1157/// let wrapped = pairs.wrap_named_list();
1158/// ```
1159pub trait AsNamedListExt: Sized {
1160    /// Wrap `self` in [`AsNamedList`] for named R list conversion.
1161    fn wrap_named_list(self) -> AsNamedList<Self> {
1162        AsNamedList(self)
1163    }
1164}
1165
1166impl<K: AsRef<str>, V: IntoR> AsNamedListExt for Vec<(K, V)> {}
1167impl<K: AsRef<str>, V: IntoR, const N: usize> AsNamedListExt for [(K, V); N] {}
1168impl<K: AsRef<str>, V: Clone + IntoR> AsNamedListExt for &[(K, V)] {}
1169
1170/// Extension trait for wrapping tuple pair collections as [`AsNamedVector`].
1171///
1172/// # Example
1173///
1174/// ```ignore
1175/// let pairs = vec![("alice".to_string(), 95.0f64), ("bob".to_string(), 87.5)];
1176/// let wrapped = pairs.wrap_named_vector();
1177/// ```
1178pub trait AsNamedVectorExt: Sized {
1179    /// Wrap `self` in [`AsNamedVector`] for named atomic R vector conversion.
1180    fn wrap_named_vector(self) -> AsNamedVector<Self> {
1181        AsNamedVector(self)
1182    }
1183}
1184
1185impl<K: AsRef<str>, V: AtomicElement> AsNamedVectorExt for Vec<(K, V)> {}
1186impl<K: AsRef<str>, V: AtomicElement, const N: usize> AsNamedVectorExt for [(K, V); N] {}
1187impl<K: AsRef<str>, V: Clone + AtomicElement> AsNamedVectorExt for &[(K, V)] {}
1188// endregion
1189
1190// region: Display/FromStr trait adapters
1191
1192/// Wrap a `T: Display` and convert it to an R character scalar.
1193///
1194/// Any type implementing `std::fmt::Display` can be returned to R as a string
1195/// without implementing miniextendr traits.
1196///
1197/// # Example
1198///
1199/// ```ignore
1200/// use std::net::IpAddr;
1201///
1202/// #[miniextendr]
1203/// fn format_ip(ip: &str) -> AsDisplay<IpAddr> {
1204///     AsDisplay(ip.parse().unwrap())
1205/// }
1206/// // R gets: "192.168.1.1"
1207/// ```
1208#[derive(Debug, Clone, Copy)]
1209pub struct AsDisplay<T>(pub T);
1210
1211impl<T: std::fmt::Display> IntoR for AsDisplay<T> {
1212    type Error = std::convert::Infallible;
1213
1214    #[inline]
1215    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1216        Ok(self.0.to_string().into_sexp())
1217    }
1218
1219    #[inline]
1220    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1221        Ok(unsafe { self.0.to_string().into_sexp_unchecked() })
1222    }
1223}
1224
1225/// Wrap a `Vec<T: Display>` and convert it to an R character vector.
1226///
1227/// # Example
1228///
1229/// ```ignore
1230/// #[miniextendr]
1231/// fn format_errors(errors: Vec<std::io::Error>) -> AsDisplayVec<std::io::Error> {
1232///     AsDisplayVec(errors)
1233/// }
1234/// ```
1235#[derive(Debug, Clone)]
1236pub struct AsDisplayVec<T>(pub Vec<T>);
1237
1238impl<T: std::fmt::Display> IntoR for AsDisplayVec<T> {
1239    type Error = std::convert::Infallible;
1240
1241    #[inline]
1242    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1243        let strings: Vec<String> = self.0.into_iter().map(|x| x.to_string()).collect();
1244        Ok(strings.into_sexp())
1245    }
1246
1247    #[inline]
1248    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1249        let strings: Vec<String> = self.0.into_iter().map(|x| x.to_string()).collect();
1250        Ok(unsafe { strings.into_sexp_unchecked() })
1251    }
1252}
1253
1254/// Wrap a parsed `T: FromStr` from an R character scalar.
1255///
1256/// Pass an R character scalar and it will be parsed into `T` via `str::parse()`.
1257///
1258/// # Example
1259///
1260/// ```ignore
1261/// use std::net::IpAddr;
1262///
1263/// #[miniextendr]
1264/// fn check_ip(addr: AsFromStr<IpAddr>) -> bool {
1265///     addr.0.is_loopback()
1266/// }
1267/// // R: check_ip("127.0.0.1") → TRUE
1268/// ```
1269#[derive(Debug, Clone)]
1270pub struct AsFromStr<T>(pub T);
1271
1272impl<T: std::str::FromStr> crate::from_r::TryFromSexp for AsFromStr<T>
1273where
1274    T::Err: std::fmt::Display,
1275{
1276    type Error = crate::from_r::SexpError;
1277
1278    fn try_from_sexp(sexp: crate::ffi::SEXP) -> Result<Self, Self::Error> {
1279        let s: &str = crate::from_r::TryFromSexp::try_from_sexp(sexp)?;
1280        let value = s
1281            .parse::<T>()
1282            .map_err(|e| crate::from_r::SexpError::InvalidValue(format!("{e}")))?;
1283        Ok(AsFromStr(value))
1284    }
1285
1286    unsafe fn try_from_sexp_unchecked(sexp: crate::ffi::SEXP) -> Result<Self, Self::Error> {
1287        let s: &str = unsafe { crate::from_r::TryFromSexp::try_from_sexp_unchecked(sexp)? };
1288        let value = s
1289            .parse::<T>()
1290            .map_err(|e| crate::from_r::SexpError::InvalidValue(format!("{e}")))?;
1291        Ok(AsFromStr(value))
1292    }
1293}
1294
1295/// Wrap a `Vec<T: FromStr>` parsed from an R character vector.
1296///
1297/// Each element of the R character vector is parsed into `T`.
1298/// All parse errors are collected with their indices.
1299///
1300/// # Example
1301///
1302/// ```ignore
1303/// use std::net::IpAddr;
1304///
1305/// #[miniextendr]
1306/// fn parse_ips(addrs: AsFromStrVec<IpAddr>) -> Vec<bool> {
1307///     addrs.0.into_iter().map(|ip| ip.is_loopback()).collect()
1308/// }
1309/// // R: parse_ips(c("127.0.0.1", "8.8.8.8")) → c(TRUE, FALSE)
1310/// ```
1311#[derive(Debug, Clone)]
1312pub struct AsFromStrVec<T>(pub Vec<T>);
1313
1314impl<T: std::str::FromStr> crate::from_r::TryFromSexp for AsFromStrVec<T>
1315where
1316    T::Err: std::fmt::Display,
1317{
1318    type Error = crate::from_r::SexpError;
1319
1320    fn try_from_sexp(sexp: crate::ffi::SEXP) -> Result<Self, Self::Error> {
1321        let strings: Vec<String> = crate::from_r::TryFromSexp::try_from_sexp(sexp)?;
1322        let mut result = Vec::with_capacity(strings.len());
1323        let mut errors = Vec::new();
1324        for (i, s) in strings.iter().enumerate() {
1325            match s.parse::<T>() {
1326                Ok(v) => result.push(v),
1327                Err(e) => errors.push(format!("index {i}: {e}")),
1328            }
1329        }
1330        if errors.is_empty() {
1331            Ok(AsFromStrVec(result))
1332        } else {
1333            Err(crate::from_r::SexpError::InvalidValue(format!(
1334                "parse errors: {}",
1335                errors.join("; ")
1336            )))
1337        }
1338    }
1339
1340    unsafe fn try_from_sexp_unchecked(sexp: crate::ffi::SEXP) -> Result<Self, Self::Error> {
1341        let strings: Vec<String> =
1342            unsafe { crate::from_r::TryFromSexp::try_from_sexp_unchecked(sexp)? };
1343        let mut result = Vec::with_capacity(strings.len());
1344        let mut errors = Vec::new();
1345        for (i, s) in strings.iter().enumerate() {
1346            match s.parse::<T>() {
1347                Ok(v) => result.push(v),
1348                Err(e) => errors.push(format!("index {i}: {e}")),
1349            }
1350        }
1351        if errors.is_empty() {
1352            Ok(AsFromStrVec(result))
1353        } else {
1354            Err(crate::from_r::SexpError::InvalidValue(format!(
1355                "parse errors: {}",
1356                errors.join("; ")
1357            )))
1358        }
1359    }
1360}
1361// endregion
1362
1363// region: Collect — zero-allocation iterator-to-R-vector adapters
1364
1365/// Write an `ExactSizeIterator` of native R types directly into an R vector.
1366///
1367/// Skips the intermediate `Vec` allocation — the R vector is allocated once
1368/// and the iterator writes directly into it.
1369///
1370/// Requires `ExactSizeIterator` because R vectors must know their length
1371/// at allocation time.
1372///
1373/// # Example
1374///
1375/// ```ignore
1376/// #[miniextendr]
1377/// fn sines(n: i32) -> Collect<impl ExactSizeIterator<Item = f64>> {
1378///     Collect((0..n).map(|i| (i as f64).sin()))
1379/// }
1380/// ```
1381pub struct Collect<I>(pub I);
1382
1383impl<I, T> IntoR for Collect<I>
1384where
1385    I: ExactSizeIterator<Item = T>,
1386    T: crate::ffi::RNativeType,
1387{
1388    type Error = std::convert::Infallible;
1389
1390    #[inline]
1391    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1392        Ok(self.into_sexp())
1393    }
1394
1395    #[inline]
1396    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1397        Ok(unsafe { self.into_sexp_unchecked() })
1398    }
1399
1400    #[inline]
1401    fn into_sexp(self) -> crate::ffi::SEXP {
1402        unsafe {
1403            let (sexp, dst) = crate::into_r::alloc_r_vector::<T>(self.0.len());
1404            for (slot, val) in dst.iter_mut().zip(self.0) {
1405                *slot = val;
1406            }
1407            sexp
1408        }
1409    }
1410
1411    #[inline]
1412    unsafe fn into_sexp_unchecked(self) -> crate::ffi::SEXP {
1413        unsafe {
1414            let (sexp, dst) = crate::into_r::alloc_r_vector_unchecked::<T>(self.0.len());
1415            for (slot, val) in dst.iter_mut().zip(self.0) {
1416                *slot = val;
1417            }
1418            sexp
1419        }
1420    }
1421}
1422
1423/// Write an `ExactSizeIterator` of `String` directly into an R character vector.
1424///
1425/// Strings require per-element CHARSXP allocation (no bulk `copy_from_slice`),
1426/// so this is a separate type from [`Collect`].
1427///
1428/// # Example
1429///
1430/// ```ignore
1431/// #[miniextendr]
1432/// fn upper(words: Vec<String>) -> CollectStrings<impl ExactSizeIterator<Item = String>> {
1433///     CollectStrings(words.into_iter().map(|w| w.to_uppercase()))
1434/// }
1435/// ```
1436pub struct CollectStrings<I>(pub I);
1437
1438impl<I> IntoR for CollectStrings<I>
1439where
1440    I: ExactSizeIterator<Item = String>,
1441{
1442    type Error = std::convert::Infallible;
1443
1444    #[inline]
1445    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1446        // Collect String refs for str_iter_to_strsxp.
1447        let strings: Vec<String> = self.0.collect();
1448        Ok(crate::into_r::str_iter_to_strsxp(
1449            strings.iter().map(|s| s.as_str()),
1450        ))
1451    }
1452
1453    #[inline]
1454    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1455        let strings: Vec<String> = self.0.collect();
1456        Ok(unsafe {
1457            crate::into_r::str_iter_to_strsxp_unchecked(strings.iter().map(|s| s.as_str()))
1458        })
1459    }
1460}
1461
1462/// Write an `ExactSizeIterator` of `Option<T>` directly into an R vector with NA support.
1463///
1464/// `None` values become `NA` in R. Works for `f64` and `i32`.
1465///
1466/// # Example
1467///
1468/// ```ignore
1469/// #[miniextendr]
1470/// fn with_gaps(n: i32) -> CollectNA<impl ExactSizeIterator<Item = Option<f64>>> {
1471///     CollectNA((0..n).map(|i| if i % 3 == 0 { None } else { Some(i as f64) }))
1472/// }
1473/// ```
1474pub struct CollectNA<I>(pub I);
1475
1476impl<I> IntoR for CollectNA<I>
1477where
1478    I: ExactSizeIterator<Item = Option<f64>>,
1479{
1480    type Error = std::convert::Infallible;
1481
1482    #[inline]
1483    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1484        Ok(self.into_sexp())
1485    }
1486
1487    #[inline]
1488    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1489        Ok(unsafe { self.into_sexp_unchecked() })
1490    }
1491
1492    #[inline]
1493    fn into_sexp(self) -> crate::ffi::SEXP {
1494        unsafe {
1495            let (sexp, dst) = crate::into_r::alloc_r_vector::<f64>(self.0.len());
1496            for (slot, val) in dst.iter_mut().zip(self.0) {
1497                *slot = val.unwrap_or(crate::altrep_traits::NA_REAL);
1498            }
1499            sexp
1500        }
1501    }
1502
1503    #[inline]
1504    unsafe fn into_sexp_unchecked(self) -> crate::ffi::SEXP {
1505        unsafe {
1506            let (sexp, dst) = crate::into_r::alloc_r_vector_unchecked::<f64>(self.0.len());
1507            for (slot, val) in dst.iter_mut().zip(self.0) {
1508                *slot = val.unwrap_or(crate::altrep_traits::NA_REAL);
1509            }
1510            sexp
1511        }
1512    }
1513}
1514
1515/// Write an `ExactSizeIterator` of `Option<i32>` directly into an R integer vector with NA.
1516pub struct CollectNAInt<I>(pub I);
1517
1518impl<I> IntoR for CollectNAInt<I>
1519where
1520    I: ExactSizeIterator<Item = Option<i32>>,
1521{
1522    type Error = std::convert::Infallible;
1523
1524    #[inline]
1525    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1526        Ok(self.into_sexp())
1527    }
1528
1529    #[inline]
1530    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1531        Ok(unsafe { self.into_sexp_unchecked() })
1532    }
1533
1534    #[inline]
1535    fn into_sexp(self) -> crate::ffi::SEXP {
1536        unsafe {
1537            let (sexp, dst) = crate::into_r::alloc_r_vector::<i32>(self.0.len());
1538            for (slot, val) in dst.iter_mut().zip(self.0) {
1539                *slot = val.unwrap_or(crate::altrep_traits::NA_INTEGER);
1540            }
1541            sexp
1542        }
1543    }
1544
1545    #[inline]
1546    unsafe fn into_sexp_unchecked(self) -> crate::ffi::SEXP {
1547        unsafe {
1548            let (sexp, dst) = crate::into_r::alloc_r_vector_unchecked::<i32>(self.0.len());
1549            for (slot, val) in dst.iter_mut().zip(self.0) {
1550                *slot = val.unwrap_or(crate::altrep_traits::NA_INTEGER);
1551            }
1552            sexp
1553        }
1554    }
1555}
1556// endregion