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//!
62//! ## A note on the `Collect*` adapters (naming-convention boundary)
63//!
64//! This module also exposes [`Collect`], [`CollectStrings`], [`CollectNA`], and
65//! [`CollectNAInt`]. These are representation-forcing `IntoR` wrappers in the same
66//! spirit as the `As*` family, but they deliberately **do not** carry the `As*`
67//! prefix. The distinction is structural: the `As*` wrappers wrap a *finished value*
68//! `T` and re-route its conversion (`AsList<T>`, `AsExternalPtr<T>`, `AsRNative<T>`),
69//! whereas the `Collect*` wrappers wrap an *iterator* `I: ExactSizeIterator` and
70//! materialize it directly into a freshly allocated R vector. The `As*`-of-`T` shape
71//! ("convert this value *as* a list / pointer / native scalar") does not describe what
72//! these do — `Collect` names the operation (drain an iterator into an R vector),
73//! which is the more accurate verb. This divergence is intentional and tracked; see
74//! the conversion control-surface analysis (`analysis/conversion-control-surface-2026-06-07.md`,
75//! §3.4 / §4.5) and issue #871.
76
77use crate::RNativeType;
78use crate::externalptr::{ExternalPtr, IntoExternalPtr};
79use crate::into_r::IntoR;
80use crate::list::{IntoList, List};
81use crate::named_vector::AtomicElement;
82
83/// Wrap a value and convert it to an R list via [`IntoList`] when returned from Rust.
84///
85/// Use this wrapper when you want to convert a single value to an R list without
86/// making that the default behavior for the type.
87///
88/// # Example
89///
90/// ```ignore
91/// #[derive(IntoList)]
92/// struct Point { x: f64, y: f64 }
93///
94/// #[miniextendr]
95/// fn make_point() -> AsList<Point> {
96///     AsList(Point { x: 1.0, y: 2.0 })
97/// }
98/// // In R: make_point() returns list(x = 1.0, y = 2.0)
99/// ```
100#[derive(Debug, Clone, Copy)]
101pub struct AsList<T: IntoList>(pub T);
102
103impl<T: IntoList> From<T> for AsList<T> {
104    fn from(value: T) -> Self {
105        AsList(value)
106    }
107}
108
109impl<T: IntoList> IntoR for AsList<T> {
110    type Error = std::convert::Infallible;
111
112    #[inline]
113    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
114        Ok(self.into_sexp())
115    }
116
117    #[inline]
118    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
119        self.try_into_sexp()
120    }
121
122    #[inline]
123    fn into_sexp(self) -> crate::SEXP {
124        self.0.into_list().into_sexp()
125    }
126}
127
128// The historical public `convert::IntoDataFrame` (`-> List`) is retired. Its row→List engine
129// now lives in `crate::dataframe::ColumnSource` (an internal `#[doc(hidden)]` trait that the
130// enum-flatten codegen and the new public `dataframe::IntoDataFrame` both delegate to). The
131// public verb surface is `dataframe::{IntoDataFrame, FromDataFrame}` (returning
132// `Result<DataFrame, DataFrameError>`), which mirrors `IntoR` / `TryFromSexp`.
133pub use crate::dataframe::ColumnSource;
134
135// region: Column gather/scatter helpers
136
137/// Scatter a typed column SEXP from a dense inner data frame into a new
138/// SEXP of length `n_rows`, placing `NA`/`NULL` at rows not in `present_idx`.
139///
140/// This is called by `DataFrameRow`-derived enum code to flatten struct-typed
141/// variant fields into prefixed columns of the parent data frame.
142///
143/// The output type mirrors the input:
144/// - REALSXP → REALSXP (NA_real_ fill)
145/// - INTSXP  → INTSXP  (NA_integer_ fill)
146/// - LGLSXP  → LGLSXP  (NA_logical fill)
147/// - RAWSXP  → RAWSXP  (`0x00` fill — R raw has no NA)
148/// - CPLXSXP → CPLXSXP (NA complex fill: both parts NA_real_)
149/// - STRSXP  → STRSXP  (NA_character_ fill)
150/// - VECSXP  → VECSXP  (R_NilValue fill)
151/// - anything else → VECSXP (R_NilValue fill)
152///
153/// Contiguous primitive columns (real/integer/logical/raw/complex) scatter as a
154/// flat slice write via [`scatter_native`] — the NA-filled inverse of
155/// [`gather_native`]. String and list columns stay element-by-element because
156/// they are write-barriered arrays of `SEXP` pointers, not flat buffers.
157///
158/// # Safety
159///
160/// Must be called on the R main thread. `src` must be a valid SEXP of length
161/// `>= present_idx.len()`. `n_rows` must equal the total row count of the
162/// parent data frame.
163#[doc(hidden)]
164pub unsafe fn scatter_column(
165    src: crate::SEXP,
166    present_idx: &[usize],
167    n_rows: usize,
168) -> crate::SEXP {
169    // SAFETY: caller guarantees R main thread; src is valid; n_rows is correct.
170    #[allow(unused_unsafe)]
171    unsafe {
172        use crate::{RLogical, Rcomplex, SEXPTYPE, SexpExt as _};
173
174        match src.type_of() {
175            SEXPTYPE::REALSXP => scatter_native::<f64>(src, present_idx, n_rows),
176            SEXPTYPE::INTSXP => scatter_native::<i32>(src, present_idx, n_rows),
177            SEXPTYPE::LGLSXP => scatter_native::<RLogical>(src, present_idx, n_rows),
178            SEXPTYPE::RAWSXP => scatter_native::<u8>(src, present_idx, n_rows),
179            SEXPTYPE::CPLXSXP => scatter_native::<Rcomplex>(src, present_idx, n_rows),
180            SEXPTYPE::STRSXP => {
181                let out = crate::sys::Rf_allocVector(SEXPTYPE::STRSXP, n_rows as isize);
182                // Write-barriered CHARSXP-pointer array — fill NA_character_, then
183                // scatter present cells element-by-element.
184                for i in 0..(n_rows as isize) {
185                    out.set_string_elt(i, crate::SEXP::na_string());
186                }
187                for (pi, &row_i) in present_idx.iter().enumerate() {
188                    out.set_string_elt(row_i as isize, src.string_elt(pi as isize));
189                }
190                out
191            }
192            SEXPTYPE::VECSXP => {
193                let out = crate::sys::Rf_allocVector(SEXPTYPE::VECSXP, n_rows as isize);
194                // Write-barriered SEXP-pointer array. R_NilValue fill is automatic
195                // (Rf_allocVector zero-initialises VECSXP slots).
196                for (pi, &row_i) in present_idx.iter().enumerate() {
197                    out.set_vector_elt(row_i as isize, src.vector_elt(pi as isize));
198                }
199                out
200            }
201            _ => {
202                // Unknown/unsupported type — produce a VECSXP list-column.
203                // Cells for absent rows remain R_NilValue.
204                let out = crate::sys::Rf_allocVector(SEXPTYPE::VECSXP, n_rows as isize);
205                for (pi, &row_i) in present_idx.iter().enumerate() {
206                    out.set_vector_elt(row_i as isize, src.vector_elt(pi as isize));
207                }
208                out
209            }
210        }
211    }
212}
213
214/// Scatter a dense typed column into a fresh SEXP of length `n_rows`, placing the
215/// `j`-th source value at row `present_idx[j]` and the per-type NA sentinel
216/// ([`RNativeType::R_NA`]) at every absent row.
217///
218/// The sparse-placing inverse of [`gather_native`]. Generic over the R element
219/// type: `f64`/`i32`/`RLogical`/`u8`/`Rcomplex` each resolve to the correct
220/// storage via their `RNativeType` impl, so the whole copy is a flat slice write
221/// with no per-element FFI call. Raw (`u8`) has no R NA, so absent positions
222/// become `0x00`.
223///
224/// # Safety
225///
226/// R main thread; `src` must be a `T::SEXP_TYPE` vector with at least
227/// `present_idx.len()` elements, and every index in `present_idx` must be
228/// `< n_rows`. The returned SEXP is unprotected (see [`scatter_column`]). No
229/// allocation occurs between allocating `out` and filling it, so `out` cannot be
230/// reaped mid-scatter.
231#[inline]
232unsafe fn scatter_native<T: RNativeType + Copy>(
233    src: crate::SEXP,
234    present_idx: &[usize],
235    n_rows: usize,
236) -> crate::SEXP {
237    unsafe {
238        use crate::SexpExt as _;
239        let src_vals: &[T] = src.as_slice::<T>();
240        let out = crate::sys::Rf_allocVector(T::SEXP_TYPE, n_rows as isize);
241        let out_vals: &mut [T] = out.as_mut_slice::<T>();
242        // NA-fill the whole output, then place present values. No allocation
243        // between alloc and fill — `out` is GC-safe.
244        out_vals.fill(T::R_NA);
245        for (pi, &row_i) in present_idx.iter().enumerate() {
246            out_vals[row_i] = src_vals[pi];
247        }
248        out
249    }
250}
251
252/// Gather the rows at `idx` (0-based, in order) from a contiguous primitive
253/// column into a fresh dense vector, where `out[j] = src[idx[j]]`.
254///
255/// Generic over the R element type: `f64`/`i32`/`RLogical`/`u8`/`Rcomplex` each
256/// resolve to the correct `REAL`/`INTEGER`/`LOGICAL`/`RAW`/`COMPLEX` storage via
257/// their `RNativeType` impl, so the copy is a plain slice gather with no
258/// per-element FFI call.
259///
260/// # Safety
261///
262/// R main thread; `src` must be a `T::SEXP_TYPE` vector and every index in `idx`
263/// must be `< xlength(src)`. The returned SEXP is unprotected (see
264/// [`gather_column`]). No allocation occurs between allocating `out` and filling
265/// it, so `out` cannot be reaped mid-gather.
266#[inline]
267unsafe fn gather_native<T: RNativeType + Copy>(src: crate::SEXP, idx: &[usize]) -> crate::SEXP {
268    unsafe {
269        use crate::SexpExt as _;
270        let src_vals: &[T] = src.as_slice::<T>();
271        let out = crate::sys::Rf_allocVector(T::SEXP_TYPE, idx.len() as isize);
272        let out_vals: &mut [T] = out.as_mut_slice::<T>();
273        for (dst, &row_i) in out_vals.iter_mut().zip(idx) {
274            *dst = src_vals[row_i];
275        }
276        out
277    }
278}
279
280/// Gather the rows at `idx` (0-based, in order) out of a typed column SEXP into a
281/// new dense SEXP of length `idx.len()`, where `out[j] = src[idx[j]]`.
282///
283/// The row-selecting inverse of [`scatter_column`]: where `scatter_column`
284/// places a dense column's values at sparse positions, `gather_column` pulls a
285/// dense subset out of a column by row index. Used by `DataFrame::select_rows`
286/// to densify a flattened sub-frame before the enum reader recurses.
287///
288/// Contiguous primitive columns (real/integer/logical/raw/complex) are copied as
289/// a slice gather via [`gather_native`]; string and list columns are copied
290/// element-by-element because they are write-barriered arrays of `SEXP` pointers,
291/// not flat buffers. The output type mirrors the input; any other type falls back
292/// to a logical `NA` column (normal `data.frame` columns never reach it).
293///
294/// Column attributes (`class`/`levels` for factor / Date / POSIXct) are **not**
295/// copied — the caller restores those after rooting the gathered column.
296///
297/// # Safety
298///
299/// Must be called on the R main thread. `src` must be a valid SEXP and every
300/// index in `idx` must be `< xlength(src)`. The returned SEXP is unprotected;
301/// the caller must root it (e.g. via `SET_VECTOR_ELT` into a protected list)
302/// before performing any allocation.
303#[doc(hidden)]
304pub unsafe fn gather_column(src: crate::SEXP, idx: &[usize]) -> crate::SEXP {
305    // SAFETY: caller guarantees R main thread, a valid `src`, and in-range indices.
306    #[allow(unused_unsafe)]
307    unsafe {
308        use crate::{RLogical, Rcomplex, SEXPTYPE, SexpExt as _};
309
310        match src.type_of() {
311            SEXPTYPE::REALSXP => gather_native::<f64>(src, idx),
312            SEXPTYPE::INTSXP => gather_native::<i32>(src, idx),
313            SEXPTYPE::LGLSXP => gather_native::<RLogical>(src, idx),
314            SEXPTYPE::RAWSXP => gather_native::<u8>(src, idx),
315            SEXPTYPE::CPLXSXP => gather_native::<Rcomplex>(src, idx),
316            SEXPTYPE::STRSXP => {
317                // Write-barriered CHARSXP-pointer array — copy element-by-element.
318                let out = crate::sys::Rf_allocVector(SEXPTYPE::STRSXP, idx.len() as isize);
319                for (j, &row_i) in idx.iter().enumerate() {
320                    out.set_string_elt(j as isize, src.string_elt(row_i as isize));
321                }
322                out
323            }
324            SEXPTYPE::VECSXP => {
325                // Write-barriered SEXP-pointer array (list-column) — element-by-element.
326                let out = crate::sys::Rf_allocVector(SEXPTYPE::VECSXP, idx.len() as isize);
327                for (j, &row_i) in idx.iter().enumerate() {
328                    out.set_vector_elt(j as isize, src.vector_elt(row_i as isize));
329                }
330                out
331            }
332            _ => {
333                // Unknown SEXPTYPE: fall back to a logical NA column of the right length.
334                let out = crate::sys::Rf_allocVector(SEXPTYPE::LGLSXP, idx.len() as isize);
335                let na_vals: &mut [RLogical] = out.as_mut_slice::<RLogical>();
336                na_vals.fill(RLogical::NA);
337                out
338            }
339        }
340    }
341}
342// endregion
343
344/// Wrap a value and convert it to an R external pointer when returned from Rust.
345///
346/// Use this wrapper when you want to return a Rust value as an opaque pointer
347/// that R code can pass back to Rust functions later.
348///
349/// # Example
350///
351/// ```ignore
352/// struct Connection { handle: u64 }
353///
354/// impl IntoExternalPtr for Connection { /* ... */ }
355///
356/// #[miniextendr]
357/// fn open_connection(path: &str) -> AsExternalPtr<Connection> {
358///     AsExternalPtr(Connection { handle: 42 })
359/// }
360/// // In R: open_connection("foo") returns an external pointer
361/// ```
362#[derive(Debug, Clone, Copy)]
363pub struct AsExternalPtr<T: IntoExternalPtr>(pub T);
364
365impl<T: IntoExternalPtr> From<T> for AsExternalPtr<T> {
366    fn from(value: T) -> Self {
367        AsExternalPtr(value)
368    }
369}
370
371impl<T: IntoExternalPtr> IntoR for AsExternalPtr<T> {
372    type Error = std::convert::Infallible;
373
374    #[inline]
375    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
376        Ok(self.into_sexp())
377    }
378
379    #[inline]
380    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
381        self.try_into_sexp()
382    }
383
384    #[inline]
385    fn into_sexp(self) -> crate::SEXP {
386        ExternalPtr::new(self.0).into_sexp()
387    }
388}
389
390/// Wrap a scalar [`RNativeType`] and force native R vector conversion.
391///
392/// This creates a length-1 R vector containing the scalar value. Use this when
393/// you want to ensure a value is converted to its native R representation (e.g.,
394/// `i32` → integer vector, `f64` → numeric vector) rather than another path
395/// like `IntoExternalPtr`.
396///
397/// # Example
398///
399/// ```ignore
400/// #[derive(Clone, Copy, RNativeType)]
401/// struct Meters(f64);
402///
403/// #[miniextendr]
404/// fn distance() -> AsRNative<Meters> {
405///     AsRNative(Meters(42.5))
406/// }
407/// // In R: distance() returns 42.5 (numeric vector of length 1)
408/// ```
409///
410/// # Performance
411///
412/// This wrapper directly allocates an R vector and writes the value,
413/// avoiding intermediate Rust allocations.
414#[derive(Debug, Clone, Copy)]
415pub struct AsRNative<T: RNativeType>(pub T);
416
417impl<T: RNativeType> From<T> for AsRNative<T> {
418    fn from(value: T) -> Self {
419        AsRNative(value)
420    }
421}
422
423impl<T: RNativeType> IntoR for AsRNative<T> {
424    type Error = std::convert::Infallible;
425
426    #[inline]
427    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
428        Ok(self.into_sexp())
429    }
430
431    #[inline]
432    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
433        Ok(unsafe { self.into_sexp_unchecked() })
434    }
435
436    #[inline]
437    fn into_sexp(self) -> crate::SEXP {
438        // Directly allocate a length-1 R vector and write the scalar value.
439        // This avoids the intermediate Rust Vec allocation.
440        unsafe {
441            let sexp = crate::sys::Rf_allocVector(T::SEXP_TYPE, 1);
442            let ptr = T::dataptr_mut(sexp);
443            std::ptr::write(ptr, self.0);
444            sexp
445        }
446    }
447
448    #[inline]
449    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
450        unsafe {
451            let sexp = crate::sys::Rf_allocVector_unchecked(T::SEXP_TYPE, 1);
452            let ptr = T::dataptr_mut(sexp);
453            std::ptr::write(ptr, self.0);
454            sexp
455        }
456    }
457}
458// endregion
459
460// region: DataFrame / Vctrs representation wrappers
461
462/// Wrap a value and convert it to an R `data.frame` via [`IntoDataFrame`](crate::dataframe::IntoDataFrame) when returned.
463///
464/// Use this at a call site to force a single return value into a data.frame without making
465/// that the type's default representation (for the always-a-data.frame default, use
466/// `#[derive(PreferDataFrame)]` / `#[miniextendr(dataframe)]`). The inner `T` is typically a
467/// `Vec<Row>` where `Row` derives [`DataFrameRow`](crate::markers::DataFrameRow).
468///
469/// A failed conversion ([`DataFrameError`](crate::dataframe::DataFrameError)) surfaces in R as
470/// an error condition.
471///
472/// # Example
473///
474/// ```ignore
475/// #[derive(DataFrameRow)]
476/// struct Point { x: f64, y: f64 }
477///
478/// #[miniextendr]
479/// fn grid() -> AsDataFrame<Vec<Point>> {
480///     AsDataFrame(vec![Point { x: 0.0, y: 0.0 }, Point { x: 1.0, y: 1.0 }])
481/// }
482/// // In R: grid() returns a data.frame with columns x, y
483/// ```
484#[derive(Debug, Clone)]
485pub struct AsDataFrame<T: crate::dataframe::IntoDataFrame>(pub T);
486
487impl<T: crate::dataframe::IntoDataFrame> From<T> for AsDataFrame<T> {
488    fn from(value: T) -> Self {
489        AsDataFrame(value)
490    }
491}
492
493impl<T: crate::dataframe::IntoDataFrame> IntoR for AsDataFrame<T> {
494    type Error = crate::dataframe::DataFrameError;
495
496    #[inline]
497    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
498        Ok(self.0.into_dataframe()?.into_sexp())
499    }
500
501    #[inline]
502    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
503        self.try_into_sexp()
504    }
505}
506
507/// Wrap a value and convert it to a **vctrs** S3 vector via [`IntoVctrs`](crate::vctrs::IntoVctrs)
508/// when returned.
509///
510/// Use this at a call site to return a `#[derive(Vctrs)]` type as its R vctrs object without the
511/// manual `value.into_vctrs().map_err(...)` boilerplate. For a type that should *always* convert
512/// this way, use `#[derive(Vctrs, PreferVctrs)]` instead.
513///
514/// A failed build ([`VctrsBuildError`](crate::vctrs::VctrsBuildError)) surfaces in R as an error
515/// condition.
516///
517/// # Example
518///
519/// ```ignore
520/// #[derive(Vctrs)]
521/// #[vctrs(class = "percent", base = "double")]
522/// struct Percent { #[vctrs(data)] values: Vec<f64> }
523///
524/// #[miniextendr]
525/// fn percent(x: Vec<f64>) -> AsVctrs<Percent> {
526///     AsVctrs(Percent { values: x })
527/// }
528/// ```
529#[cfg(feature = "vctrs")]
530#[derive(Debug, Clone)]
531pub struct AsVctrs<T: crate::vctrs::IntoVctrs>(pub T);
532
533#[cfg(feature = "vctrs")]
534impl<T: crate::vctrs::IntoVctrs> From<T> for AsVctrs<T> {
535    fn from(value: T) -> Self {
536        AsVctrs(value)
537    }
538}
539
540#[cfg(feature = "vctrs")]
541impl<T: crate::vctrs::IntoVctrs> IntoR for AsVctrs<T> {
542    type Error = crate::vctrs::VctrsBuildError;
543
544    #[inline]
545    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
546        self.0.into_vctrs()
547    }
548
549    #[inline]
550    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
551        self.try_into_sexp()
552    }
553}
554// endregion
555
556// region: Named pair wrappers
557
558/// Wrap a tuple pair collection and convert it to a **named R list** (VECSXP).
559///
560/// Preserves insertion order and allows duplicate names (sequence semantics).
561///
562/// # Supported input types
563///
564/// | Input | Bounds |
565/// |-------|--------|
566/// | `Vec<(K, V)>` | `K: AsRef<str>`, `V: IntoR` |
567/// | `[(K, V); N]` | `K: AsRef<str>`, `V: IntoR` |
568/// | `&[(K, V)]` | `K: AsRef<str>`, `V: Clone + IntoR` |
569///
570/// # GC safety — use *typed* values, never raw `SEXP` (#1030)
571///
572/// Prefer typed value types (`i32`, `String`, `Vec<T>`, …) over raw
573/// [`crate::SEXP`]. With a typed `V`, each value's `into_sexp()` runs *inside*
574/// [`AsNamedList::into_sexp`][IntoR::into_sexp], immediately before the value is
575/// protected and placed into the list — no allocation ever fires while a sibling
576/// value sits unprotected.
577///
578/// A raw `SEXP` value (`V = SEXP`) is **GC-fragile and must not be used**. The
579/// caller has to build the `SEXP` values *in an earlier frame* (each `into_sexp()`
580/// allocates) and then hand them to `AsNamedList`; the deferred `from_raw_pairs`
581/// runs later, in the generated wrapper, so there is **no call site at which a
582/// `ProtectScope` can span both value construction and the deferred list build**.
583/// The internal protection added in `into_sexp` (below) closes the *receipt →
584/// `from_raw_pairs`* window but cannot retroactively protect values that were
585/// already built and unrooted in the caller — that *construction* window is the
586/// latent use-after-free. Stable Rust cannot exclude one concrete type from a
587/// `V: IntoR` blanket, so this is enforced by convention (and the MXL302 lint on
588/// the `into_sexp()`-in-`vec!` idiom) rather than the type system; passing typed
589/// values sidesteps the hazard entirely.
590///
591/// # Example
592///
593/// ```ignore
594/// #[miniextendr]
595/// fn make_config() -> AsNamedList<Vec<(String, i32)>> {
596///     AsNamedList(vec![
597///         ("width".into(), 100),
598///         ("height".into(), 200),
599///     ])
600/// }
601/// // In R: make_config() returns list(width = 100L, height = 200L)
602/// ```
603#[derive(Debug, Clone)]
604pub struct AsNamedList<T>(pub T);
605
606impl<T> From<T> for AsNamedList<T> {
607    fn from(value: T) -> Self {
608        AsNamedList(value)
609    }
610}
611
612impl<K: AsRef<str>, V: IntoR> IntoR for AsNamedList<Vec<(K, V)>> {
613    type Error = std::convert::Infallible;
614
615    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
616        Ok(self.into_sexp())
617    }
618
619    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
620        self.try_into_sexp()
621    }
622
623    fn into_sexp(self) -> crate::SEXP {
624        // SAFETY: `into_sexp` for `#[miniextendr]` return values runs on the R
625        // main thread. Each value's `into_sexp()` is built and immediately
626        // `protect_raw`-rooted before the next value's allocation, mirroring the
627        // `#[derive(IntoList)]` codegen — no sibling sits unprotected across an
628        // allocation. The scope drops after `from_raw_pairs` returns. (#1030)
629        unsafe {
630            let scope = crate::gc_protect::ProtectScope::new();
631            let pairs: Vec<(K, crate::SEXP)> = self
632                .0
633                .into_iter()
634                .map(|(k, v)| (k, scope.protect_raw(v.into_sexp())))
635                .collect();
636            List::from_raw_pairs(pairs).into_sexp()
637        }
638    }
639}
640
641impl<K: AsRef<str>, V: IntoR, const N: usize> IntoR for AsNamedList<[(K, V); N]> {
642    type Error = std::convert::Infallible;
643
644    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
645        Ok(self.into_sexp())
646    }
647
648    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
649        self.try_into_sexp()
650    }
651
652    fn into_sexp(self) -> crate::SEXP {
653        // SAFETY: see the `Vec<(K, V)>` impl above — main thread, each value
654        // rooted before the next allocates. (#1030)
655        unsafe {
656            let scope = crate::gc_protect::ProtectScope::new();
657            let pairs: Vec<(K, crate::SEXP)> = self
658                .0
659                .into_iter()
660                .map(|(k, v)| (k, scope.protect_raw(v.into_sexp())))
661                .collect();
662            List::from_raw_pairs(pairs).into_sexp()
663        }
664    }
665}
666
667impl<K: AsRef<str>, V: Clone + IntoR> IntoR for AsNamedList<&[(K, V)]> {
668    type Error = std::convert::Infallible;
669
670    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
671        Ok(self.into_sexp())
672    }
673
674    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
675        self.try_into_sexp()
676    }
677
678    fn into_sexp(self) -> crate::SEXP {
679        // SAFETY: see the `Vec<(K, V)>` impl above — main thread, each value
680        // rooted before the next allocates. (#1030)
681        unsafe {
682            let scope = crate::gc_protect::ProtectScope::new();
683            let pairs: Vec<(&K, crate::SEXP)> = self
684                .0
685                .iter()
686                .map(|(k, v)| (k, scope.protect_raw(v.clone().into_sexp())))
687                .collect();
688            List::from_raw_pairs(pairs).into_sexp()
689        }
690    }
691}
692
693/// Wrap a tuple pair collection and convert it to a **named atomic R vector**
694/// (INTSXP, REALSXP, LGLSXP, RAWSXP, or STRSXP).
695///
696/// Preserves insertion order and allows duplicate names (sequence semantics).
697/// Values must be homogeneous and implement [`AtomicElement`].
698///
699/// # Supported input types
700///
701/// | Input | Bounds |
702/// |-------|--------|
703/// | `Vec<(K, V)>` | `K: AsRef<str>`, `V: AtomicElement` |
704/// | `[(K, V); N]` | `K: AsRef<str>`, `V: AtomicElement` |
705/// | `&[(K, V)]` | `K: AsRef<str>`, `V: Clone + AtomicElement` |
706///
707/// # Example
708///
709/// ```ignore
710/// #[miniextendr]
711/// fn make_scores() -> AsNamedVector<Vec<(&str, f64)>> {
712///     AsNamedVector(vec![("alice", 95.0), ("bob", 87.5)])
713/// }
714/// // In R: make_scores() returns c(alice = 95.0, bob = 87.5)
715/// ```
716#[derive(Debug, Clone)]
717pub struct AsNamedVector<T>(pub T);
718
719impl<T> From<T> for AsNamedVector<T> {
720    fn from(value: T) -> Self {
721        AsNamedVector(value)
722    }
723}
724
725impl<K: AsRef<str>, V: AtomicElement> IntoR for AsNamedVector<Vec<(K, V)>> {
726    type Error = std::convert::Infallible;
727
728    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
729        Ok(self.into_sexp())
730    }
731
732    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
733        self.try_into_sexp()
734    }
735
736    fn into_sexp(self) -> crate::SEXP {
737        named_vector_from_pairs(self.0)
738    }
739}
740
741impl<K: AsRef<str>, V: AtomicElement, const N: usize> IntoR for AsNamedVector<[(K, V); N]> {
742    type Error = std::convert::Infallible;
743
744    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
745        Ok(self.into_sexp())
746    }
747
748    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
749        self.try_into_sexp()
750    }
751
752    fn into_sexp(self) -> crate::SEXP {
753        named_vector_from_pairs(self.0)
754    }
755}
756
757impl<K: AsRef<str>, V: Clone + AtomicElement> IntoR for AsNamedVector<&[(K, V)]> {
758    type Error = std::convert::Infallible;
759
760    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
761        Ok(self.into_sexp())
762    }
763
764    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
765        self.try_into_sexp()
766    }
767
768    fn into_sexp(self) -> crate::SEXP {
769        let (keys, values): (Vec<&K>, Vec<V>) = self.0.iter().map(|(k, v)| (k, v.clone())).unzip();
770        let sexp = V::vec_to_sexp(values);
771        unsafe {
772            crate::sys::Rf_protect(sexp);
773            crate::named_vector::set_names_on_sexp(sexp, &keys);
774            crate::sys::Rf_unprotect(1);
775        }
776        sexp
777    }
778}
779
780/// Shared helper: build a named atomic vector from an owning iterator of (key, value) pairs.
781fn named_vector_from_pairs<K, V>(pairs: impl IntoIterator<Item = (K, V)>) -> crate::SEXP
782where
783    K: AsRef<str>,
784    V: AtomicElement,
785{
786    let (keys, values): (Vec<K>, Vec<V>) = pairs.into_iter().unzip();
787    let sexp = V::vec_to_sexp(values);
788    unsafe {
789        crate::sys::Rf_protect(sexp);
790        crate::named_vector::set_names_on_sexp(sexp, &keys);
791        crate::sys::Rf_unprotect(1);
792    }
793    sexp
794}
795// endregion
796
797// region: Extension traits for ergonomic wrapping
798//
799// These extension traits provide method-style wrapping that works even when
800// the destination type isn't constrained (i.e., `value.wrap_list()` instead
801// of `value.into()` which requires type inference).
802//
803// ```ignore
804// // These all work without type annotations:
805// let wrapped = my_struct.wrap_list();
806// let ptr = my_value.wrap_external_ptr();
807// let native = my_num.wrap_r_native();
808// ```
809
810/// Extension trait for wrapping values as [`AsList`].
811///
812/// This trait is automatically implemented for all types that implement [`IntoList`].
813///
814/// # Example
815///
816/// ```ignore
817/// use miniextendr_api::convert::AsListExt;
818///
819/// #[derive(IntoList)]
820/// struct Point { x: f64, y: f64 }
821///
822/// let point = Point { x: 1.0, y: 2.0 };
823/// let wrapped: AsList<Point> = point.wrap_list();
824/// ```
825pub trait AsListExt: IntoList + Sized {
826    /// Wrap `self` in [`AsList`] for R list conversion.
827    fn wrap_list(self) -> AsList<Self> {
828        AsList(self)
829    }
830}
831
832impl<T: IntoList> AsListExt for T {}
833
834/// Extension trait for wrapping values as [`AsExternalPtr`].
835///
836/// This trait is automatically implemented for all types that implement [`IntoExternalPtr`].
837///
838/// # Example
839///
840/// ```ignore
841/// use miniextendr_api::convert::AsExternalPtrExt;
842///
843/// #[derive(ExternalPtr)]
844/// struct Connection { handle: u64 }
845///
846/// let conn = Connection { handle: 42 };
847/// let wrapped: AsExternalPtr<Connection> = conn.wrap_external_ptr();
848/// ```
849pub trait AsExternalPtrExt: IntoExternalPtr + Sized {
850    /// Wrap `self` in [`AsExternalPtr`] for R external pointer conversion.
851    fn wrap_external_ptr(self) -> AsExternalPtr<Self> {
852        AsExternalPtr(self)
853    }
854}
855
856impl<T: IntoExternalPtr> AsExternalPtrExt for T {}
857
858/// Extension trait for wrapping values as [`AsRNative`].
859///
860/// This trait is automatically implemented for all types that implement [`RNativeType`].
861///
862/// # Example
863///
864/// ```ignore
865/// use miniextendr_api::convert::AsRNativeExt;
866///
867/// let x: f64 = 42.5;
868/// let wrapped: AsRNative<f64> = x.wrap_r_native();
869/// ```
870pub trait AsRNativeExt: RNativeType + Sized {
871    /// Wrap `self` in [`AsRNative`] for native R scalar conversion.
872    fn wrap_r_native(self) -> AsRNative<Self> {
873        AsRNative(self)
874    }
875}
876
877impl<T: RNativeType> AsRNativeExt for T {}
878
879/// Extension trait for wrapping values as [`AsDataFrame`].
880///
881/// Automatically implemented for all `T: IntoDataFrame` (typically `Vec<Row>` where `Row`
882/// derives `DataFrameRow`).
883pub trait AsDataFrameExt: crate::dataframe::IntoDataFrame + Sized {
884    /// Wrap `self` in [`AsDataFrame`] for R data.frame conversion.
885    fn wrap_data_frame(self) -> AsDataFrame<Self> {
886        AsDataFrame(self)
887    }
888}
889
890impl<T: crate::dataframe::IntoDataFrame> AsDataFrameExt for T {}
891
892/// Extension trait for wrapping values as [`AsVctrs`].
893///
894/// Automatically implemented for all `T: IntoVctrs` (typically a `#[derive(Vctrs)]` type).
895#[cfg(feature = "vctrs")]
896pub trait AsVctrsExt: crate::vctrs::IntoVctrs + Sized {
897    /// Wrap `self` in [`AsVctrs`] for R vctrs conversion.
898    fn wrap_vctrs(self) -> AsVctrs<Self> {
899        AsVctrs(self)
900    }
901}
902
903#[cfg(feature = "vctrs")]
904impl<T: crate::vctrs::IntoVctrs> AsVctrsExt for T {}
905
906/// Extension trait for wrapping tuple pair collections as [`AsNamedList`].
907///
908/// # Example
909///
910/// ```ignore
911/// let pairs = vec![("x".to_string(), 1i32), ("y".to_string(), 2i32)];
912/// let wrapped = pairs.wrap_named_list();
913/// ```
914pub trait AsNamedListExt: Sized {
915    /// Wrap `self` in [`AsNamedList`] for named R list conversion.
916    fn wrap_named_list(self) -> AsNamedList<Self> {
917        AsNamedList(self)
918    }
919}
920
921impl<K: AsRef<str>, V: IntoR> AsNamedListExt for Vec<(K, V)> {}
922impl<K: AsRef<str>, V: IntoR, const N: usize> AsNamedListExt for [(K, V); N] {}
923impl<K: AsRef<str>, V: Clone + IntoR> AsNamedListExt for &[(K, V)] {}
924
925/// Extension trait for wrapping tuple pair collections as [`AsNamedVector`].
926///
927/// # Example
928///
929/// ```ignore
930/// let pairs = vec![("alice".to_string(), 95.0f64), ("bob".to_string(), 87.5)];
931/// let wrapped = pairs.wrap_named_vector();
932/// ```
933pub trait AsNamedVectorExt: Sized {
934    /// Wrap `self` in [`AsNamedVector`] for named atomic R vector conversion.
935    fn wrap_named_vector(self) -> AsNamedVector<Self> {
936        AsNamedVector(self)
937    }
938}
939
940impl<K: AsRef<str>, V: AtomicElement> AsNamedVectorExt for Vec<(K, V)> {}
941impl<K: AsRef<str>, V: AtomicElement, const N: usize> AsNamedVectorExt for [(K, V); N] {}
942impl<K: AsRef<str>, V: Clone + AtomicElement> AsNamedVectorExt for &[(K, V)] {}
943// endregion
944
945// region: Display/FromStr trait adapters
946
947/// Wrap a `T: Display` and convert it to an R character scalar.
948///
949/// Any type implementing `std::fmt::Display` can be returned to R as a string
950/// without implementing miniextendr traits.
951///
952/// # Example
953///
954/// ```ignore
955/// use std::net::IpAddr;
956///
957/// #[miniextendr]
958/// fn format_ip(ip: &str) -> AsDisplay<IpAddr> {
959///     AsDisplay(ip.parse().unwrap())
960/// }
961/// // R gets: "192.168.1.1"
962/// ```
963#[derive(Debug, Clone, Copy)]
964pub struct AsDisplay<T>(pub T);
965
966impl<T: std::fmt::Display> IntoR for AsDisplay<T> {
967    type Error = std::convert::Infallible;
968
969    #[inline]
970    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
971        Ok(self.0.to_string().into_sexp())
972    }
973
974    #[inline]
975    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
976        Ok(unsafe { self.0.to_string().into_sexp_unchecked() })
977    }
978}
979
980/// Wrap a `Vec<T: Display>` and convert it to an R character vector.
981///
982/// # Example
983///
984/// ```ignore
985/// #[miniextendr]
986/// fn format_errors(errors: Vec<std::io::Error>) -> AsDisplayVec<std::io::Error> {
987///     AsDisplayVec(errors)
988/// }
989/// ```
990#[derive(Debug, Clone)]
991pub struct AsDisplayVec<T>(pub Vec<T>);
992
993impl<T: std::fmt::Display> IntoR for AsDisplayVec<T> {
994    type Error = std::convert::Infallible;
995
996    #[inline]
997    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
998        let strings: Vec<String> = self.0.into_iter().map(|x| x.to_string()).collect();
999        Ok(strings.into_sexp())
1000    }
1001
1002    #[inline]
1003    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
1004        let strings: Vec<String> = self.0.into_iter().map(|x| x.to_string()).collect();
1005        Ok(unsafe { strings.into_sexp_unchecked() })
1006    }
1007}
1008
1009/// Wrap a parsed `T: FromStr` from an R character scalar.
1010///
1011/// Pass an R character scalar and it will be parsed into `T` via `str::parse()`.
1012///
1013/// # Example
1014///
1015/// ```ignore
1016/// use std::net::IpAddr;
1017///
1018/// #[miniextendr]
1019/// fn check_ip(addr: AsFromStr<IpAddr>) -> bool {
1020///     addr.0.is_loopback()
1021/// }
1022/// // R: check_ip("127.0.0.1") → TRUE
1023/// ```
1024#[derive(Debug, Clone)]
1025pub struct AsFromStr<T>(pub T);
1026
1027impl<T: std::str::FromStr> crate::from_r::TryFromSexp for AsFromStr<T>
1028where
1029    T::Err: std::fmt::Display,
1030{
1031    type Error = crate::from_r::SexpError;
1032
1033    fn try_from_sexp(sexp: crate::SEXP) -> Result<Self, Self::Error> {
1034        let s: &str = crate::from_r::TryFromSexp::try_from_sexp(sexp)?;
1035        let value = s
1036            .parse::<T>()
1037            .map_err(|e| crate::from_r::SexpError::InvalidValue(format!("{e}")))?;
1038        Ok(AsFromStr(value))
1039    }
1040
1041    unsafe fn try_from_sexp_unchecked(sexp: crate::SEXP) -> Result<Self, Self::Error> {
1042        let s: &str = unsafe { crate::from_r::TryFromSexp::try_from_sexp_unchecked(sexp)? };
1043        let value = s
1044            .parse::<T>()
1045            .map_err(|e| crate::from_r::SexpError::InvalidValue(format!("{e}")))?;
1046        Ok(AsFromStr(value))
1047    }
1048}
1049
1050/// Wrap a `Vec<T: FromStr>` parsed from an R character vector.
1051///
1052/// Each element of the R character vector is parsed into `T`.
1053/// All parse errors are collected with their indices.
1054///
1055/// # Example
1056///
1057/// ```ignore
1058/// use std::net::IpAddr;
1059///
1060/// #[miniextendr]
1061/// fn parse_ips(addrs: AsFromStrVec<IpAddr>) -> Vec<bool> {
1062///     addrs.0.into_iter().map(|ip| ip.is_loopback()).collect()
1063/// }
1064/// // R: parse_ips(c("127.0.0.1", "8.8.8.8")) → c(TRUE, FALSE)
1065/// ```
1066#[derive(Debug, Clone)]
1067pub struct AsFromStrVec<T>(pub Vec<T>);
1068
1069impl<T: std::str::FromStr> crate::from_r::TryFromSexp for AsFromStrVec<T>
1070where
1071    T::Err: std::fmt::Display,
1072{
1073    type Error = crate::from_r::SexpError;
1074
1075    fn try_from_sexp(sexp: crate::SEXP) -> Result<Self, Self::Error> {
1076        let strings: Vec<String> = crate::from_r::TryFromSexp::try_from_sexp(sexp)?;
1077        let mut result = Vec::with_capacity(strings.len());
1078        let mut errors = Vec::new();
1079        for (i, s) in strings.iter().enumerate() {
1080            match s.parse::<T>() {
1081                Ok(v) => result.push(v),
1082                Err(e) => errors.push(format!("index {i}: {e}")),
1083            }
1084        }
1085        if errors.is_empty() {
1086            Ok(AsFromStrVec(result))
1087        } else {
1088            Err(crate::from_r::SexpError::InvalidValue(format!(
1089                "parse errors: {}",
1090                errors.join("; ")
1091            )))
1092        }
1093    }
1094
1095    unsafe fn try_from_sexp_unchecked(sexp: crate::SEXP) -> Result<Self, Self::Error> {
1096        let strings: Vec<String> =
1097            unsafe { crate::from_r::TryFromSexp::try_from_sexp_unchecked(sexp)? };
1098        let mut result = Vec::with_capacity(strings.len());
1099        let mut errors = Vec::new();
1100        for (i, s) in strings.iter().enumerate() {
1101            match s.parse::<T>() {
1102                Ok(v) => result.push(v),
1103                Err(e) => errors.push(format!("index {i}: {e}")),
1104            }
1105        }
1106        if errors.is_empty() {
1107            Ok(AsFromStrVec(result))
1108        } else {
1109            Err(crate::from_r::SexpError::InvalidValue(format!(
1110                "parse errors: {}",
1111                errors.join("; ")
1112            )))
1113        }
1114    }
1115}
1116// endregion
1117
1118// region: Collect — zero-allocation iterator-to-R-vector adapters
1119//
1120// Naming-convention exemption: the four `Collect*` types below are
1121// representation-forcing `IntoR` wrappers, like the `As*` family above, but they
1122// intentionally diverge from the `As*` prefix. `As*` wraps a finished value `T`
1123// and re-routes its conversion; `Collect*` wraps an *iterator*
1124// (`I: ExactSizeIterator`) and drains it straight into a freshly allocated R
1125// vector. The `As*`-of-`T` shape does not describe an iterator adapter, so
1126// `Collect` (the verb for the operation) is the accurate name. See the module-level
1127// docs, `analysis/conversion-control-surface-2026-06-07.md` (§3.4 / §4.5), and #871.
1128
1129/// Write an `ExactSizeIterator` of native R types directly into an R vector.
1130///
1131/// Skips the intermediate `Vec` allocation — the R vector is allocated once
1132/// and the iterator writes directly into it.
1133///
1134/// Requires `ExactSizeIterator` because R vectors must know their length
1135/// at allocation time.
1136///
1137/// # Naming
1138///
1139/// `Collect` is in the representation-forcing wrapper family but does not take the
1140/// `As*` prefix used by [`AsList`] / [`AsExternalPtr`] / [`AsRNative`]: those wrap a
1141/// finished value `T`, whereas `Collect` wraps an *iterator* and materializes it into
1142/// an R vector. The divergence is intentional — see the module docs and #871.
1143///
1144/// # Example
1145///
1146/// ```ignore
1147/// #[miniextendr]
1148/// fn sines(n: i32) -> Collect<impl ExactSizeIterator<Item = f64>> {
1149///     Collect((0..n).map(|i| (i as f64).sin()))
1150/// }
1151/// ```
1152pub struct Collect<I>(pub I);
1153
1154impl<I, T> IntoR for Collect<I>
1155where
1156    I: ExactSizeIterator<Item = T>,
1157    T: crate::RNativeType,
1158{
1159    type Error = std::convert::Infallible;
1160
1161    #[inline]
1162    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
1163        Ok(self.into_sexp())
1164    }
1165
1166    #[inline]
1167    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
1168        Ok(unsafe { self.into_sexp_unchecked() })
1169    }
1170
1171    #[inline]
1172    fn into_sexp(self) -> crate::SEXP {
1173        unsafe {
1174            let (sexp, dst) = crate::into_r::alloc_r_vector::<T>(self.0.len());
1175            for (slot, val) in dst.iter_mut().zip(self.0) {
1176                *slot = val;
1177            }
1178            sexp
1179        }
1180    }
1181
1182    #[inline]
1183    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
1184        unsafe {
1185            let (sexp, dst) = crate::into_r::alloc_r_vector_unchecked::<T>(self.0.len());
1186            for (slot, val) in dst.iter_mut().zip(self.0) {
1187                *slot = val;
1188            }
1189            sexp
1190        }
1191    }
1192}
1193
1194/// Write an `ExactSizeIterator` of `String` directly into an R character vector.
1195///
1196/// Strings require per-element CHARSXP allocation (no bulk `copy_from_slice`),
1197/// so this is a separate type from [`Collect`]. Like [`Collect`], it is an
1198/// iterator adapter and is exempt from the `As*` naming convention (see #871).
1199///
1200/// # Example
1201///
1202/// ```ignore
1203/// #[miniextendr]
1204/// fn upper(words: Vec<String>) -> CollectStrings<impl ExactSizeIterator<Item = String>> {
1205///     CollectStrings(words.into_iter().map(|w| w.to_uppercase()))
1206/// }
1207/// ```
1208pub struct CollectStrings<I>(pub I);
1209
1210impl<I> IntoR for CollectStrings<I>
1211where
1212    I: ExactSizeIterator<Item = String>,
1213{
1214    type Error = std::convert::Infallible;
1215
1216    #[inline]
1217    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
1218        // Collect String refs for str_iter_to_strsxp.
1219        let strings: Vec<String> = self.0.collect();
1220        Ok(crate::into_r::str_iter_to_strsxp(
1221            strings.iter().map(|s| s.as_str()),
1222        ))
1223    }
1224
1225    #[inline]
1226    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
1227        let strings: Vec<String> = self.0.collect();
1228        Ok(unsafe {
1229            crate::into_r::str_iter_to_strsxp_unchecked(strings.iter().map(|s| s.as_str()))
1230        })
1231    }
1232}
1233
1234/// Write an `ExactSizeIterator` of `Option<T>` directly into an R vector with NA support.
1235///
1236/// `None` values become `NA` in R. Works for `f64` and `i32`.
1237///
1238/// Like [`Collect`], this is an iterator adapter and is exempt from the `As*`
1239/// naming convention (see #871).
1240///
1241/// # Example
1242///
1243/// ```ignore
1244/// #[miniextendr]
1245/// fn with_gaps(n: i32) -> CollectNA<impl ExactSizeIterator<Item = Option<f64>>> {
1246///     CollectNA((0..n).map(|i| if i % 3 == 0 { None } else { Some(i as f64) }))
1247/// }
1248/// ```
1249pub struct CollectNA<I>(pub I);
1250
1251impl<I> IntoR for CollectNA<I>
1252where
1253    I: ExactSizeIterator<Item = Option<f64>>,
1254{
1255    type Error = std::convert::Infallible;
1256
1257    #[inline]
1258    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
1259        Ok(self.into_sexp())
1260    }
1261
1262    #[inline]
1263    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
1264        Ok(unsafe { self.into_sexp_unchecked() })
1265    }
1266
1267    #[inline]
1268    fn into_sexp(self) -> crate::SEXP {
1269        unsafe {
1270            let (sexp, dst) = crate::into_r::alloc_r_vector::<f64>(self.0.len());
1271            for (slot, val) in dst.iter_mut().zip(self.0) {
1272                *slot = val.unwrap_or(crate::altrep_traits::NA_REAL);
1273            }
1274            sexp
1275        }
1276    }
1277
1278    #[inline]
1279    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
1280        unsafe {
1281            let (sexp, dst) = crate::into_r::alloc_r_vector_unchecked::<f64>(self.0.len());
1282            for (slot, val) in dst.iter_mut().zip(self.0) {
1283                *slot = val.unwrap_or(crate::altrep_traits::NA_REAL);
1284            }
1285            sexp
1286        }
1287    }
1288}
1289
1290/// Write an `ExactSizeIterator` of `Option<i32>` directly into an R integer vector with NA.
1291///
1292/// Like [`Collect`], this is an iterator adapter and is exempt from the `As*`
1293/// naming convention (see #871).
1294pub struct CollectNAInt<I>(pub I);
1295
1296impl<I> IntoR for CollectNAInt<I>
1297where
1298    I: ExactSizeIterator<Item = Option<i32>>,
1299{
1300    type Error = std::convert::Infallible;
1301
1302    #[inline]
1303    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
1304        Ok(self.into_sexp())
1305    }
1306
1307    #[inline]
1308    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
1309        Ok(unsafe { self.into_sexp_unchecked() })
1310    }
1311
1312    #[inline]
1313    fn into_sexp(self) -> crate::SEXP {
1314        unsafe {
1315            let (sexp, dst) = crate::into_r::alloc_r_vector::<i32>(self.0.len());
1316            for (slot, val) in dst.iter_mut().zip(self.0) {
1317                *slot = val.unwrap_or(crate::altrep_traits::NA_INTEGER);
1318            }
1319            sexp
1320        }
1321    }
1322
1323    #[inline]
1324    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
1325        unsafe {
1326            let (sexp, dst) = crate::into_r::alloc_r_vector_unchecked::<i32>(self.0.len());
1327            for (slot, val) in dst.iter_mut().zip(self.0) {
1328                *slot = val.unwrap_or(crate::altrep_traits::NA_INTEGER);
1329            }
1330            sexp
1331        }
1332    }
1333}
1334// endregion