Skip to main content

miniextendr_api/
list.rs

1#![allow(rustdoc::private_intra_doc_links)]
2//! Thin wrapper around R list (`VECSXP`).
3//!
4//! Provides safe construction from Rust values and typed extraction.
5//!
6//! # Submodules
7//!
8//! | Module | Contents |
9//! |--------|----------|
10//! | [`accumulator`] | `ListAccumulator` — dynamic list construction with bounded protect stack |
11//! | [`named`] | `NamedList` — O(1) name-indexed access via `HashMap` index |
12//!
13//! # Core Types
14//!
15//! - [`List`] — owned handle to an R list (VECSXP)
16//! - [`ListMut`] — mutable view for in-place element replacement
17//! - [`ListBuilder`] — fixed-size batch construction
18//! - [`IntoList`] / [`TryFromList`] — conversion traits
19
20use crate::ffi::SEXPTYPE::{LISTSXP, STRSXP, VECSXP};
21use crate::ffi::{self, SEXP, SexpExt};
22use crate::from_r::{SexpError, SexpLengthError, SexpTypeError, TryFromSexp};
23use crate::gc_protect::OwnedProtect;
24use crate::into_r::IntoR;
25use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
26use std::hash::Hash;
27
28/// Owned handle to an R list (`VECSXP`).
29///
30/// # Examples
31///
32/// ```no_run
33/// use miniextendr_api::list::List;
34///
35/// let list = List::from_values(vec![1i32, 2, 3]);
36/// assert_eq!(list.len(), 3);
37/// let first: Option<i32> = list.get_index(0);
38/// ```
39#[derive(Clone, Copy, Debug)]
40pub struct List(SEXP);
41
42/// Mutable view of an R list (`VECSXP`).
43///
44/// This is a wrapper type instead of `&mut [SEXP]` to avoid exposing a raw slice
45/// that could become invalid if list elements are replaced with `NULL`.
46#[derive(Debug)]
47pub struct ListMut(SEXP);
48
49impl List {
50    /// Return true if the underlying SEXP is a list (VECSXP) according to R.
51    #[inline]
52    pub fn is_list(self) -> bool {
53        self.0.is_pair_list()
54    }
55
56    /// Wrap an existing `VECSXP` without additional checks.
57    ///
58    /// # Safety
59    ///
60    /// Caller must ensure `sexp` is a valid list object (typically a `VECSXP` or
61    /// a pairlist coerced to `VECSXP`) whose lifetime remains managed by R.
62    #[inline]
63    pub const unsafe fn from_raw(sexp: SEXP) -> Self {
64        List(sexp)
65    }
66
67    /// Get the underlying `SEXP`.
68    #[inline]
69    pub const fn as_sexp(self) -> SEXP {
70        self.0
71    }
72
73    /// Length of the list (number of elements).
74    #[inline]
75    pub fn len(self) -> isize {
76        unsafe { ffi::Rf_xlength(self.0) }
77    }
78
79    /// Returns true if the list is empty.
80    #[inline]
81    pub fn is_empty(self) -> bool {
82        self.len() == 0
83    }
84
85    /// Get raw SEXP element at 0-based index. Returns `None` if out of bounds.
86    #[inline]
87    pub fn get(self, idx: isize) -> Option<SEXP> {
88        if idx < 0 || idx >= self.len() {
89            return None;
90        }
91        Some(self.0.vector_elt(idx))
92    }
93
94    /// Get element at 0-based index and convert to type `T`.
95    ///
96    /// Returns `None` if index is out of bounds or conversion fails.
97    #[inline]
98    pub fn get_index<T>(self, idx: isize) -> Option<T>
99    where
100        T: TryFromSexp<Error = SexpError>,
101    {
102        let sexp = self.get(idx)?;
103        T::try_from_sexp(sexp).ok()
104    }
105
106    /// Get element by name and convert to type `T`.
107    ///
108    /// Returns `None` if name not found or conversion fails.
109    pub fn get_named<T>(self, name: &str) -> Option<T>
110    where
111        T: TryFromSexp<Error = SexpError>,
112    {
113        let names_sexp = self.names()?;
114        let n = self.len();
115
116        // Search for matching name
117        for i in 0..n {
118            let name_sexp = names_sexp.string_elt(i);
119            if name_sexp == SEXP::na_string() {
120                continue;
121            }
122            let name_ptr = name_sexp.r_char();
123            let name_cstr = unsafe { std::ffi::CStr::from_ptr(name_ptr) };
124            if let Ok(s) = name_cstr.to_str() {
125                if s == name {
126                    let elem = self.0.vector_elt(i);
127                    return T::try_from_sexp(elem).ok();
128                }
129            }
130        }
131        None
132    }
133
134    // region: Attribute getters (equivalent to R's GET_* macros)
135
136    /// Get an arbitrary attribute by symbol, returning `None` for `R_NilValue`.
137    #[inline]
138    fn get_attr_opt(self, name: SEXP) -> Option<SEXP> {
139        let attr = self.0.get_attr(name);
140        if attr.is_nil() { None } else { Some(attr) }
141    }
142
143    /// Get the `names` attribute if present.
144    #[inline]
145    pub fn names(self) -> Option<SEXP> {
146        self.get_attr_opt(SEXP::names_symbol())
147    }
148
149    /// Get the `class` attribute if present.
150    #[inline]
151    pub fn get_class(self) -> Option<SEXP> {
152        self.get_attr_opt(SEXP::class_symbol())
153    }
154
155    /// Get the `dim` attribute if present.
156    #[inline]
157    pub fn get_dim(self) -> Option<SEXP> {
158        self.get_attr_opt(SEXP::dim_symbol())
159    }
160
161    /// Get the `dimnames` attribute if present.
162    #[inline]
163    pub fn get_dimnames(self) -> Option<SEXP> {
164        self.get_attr_opt(SEXP::dimnames_symbol())
165    }
166
167    /// Get row names from the `dimnames` attribute.
168    #[inline]
169    pub fn get_rownames(self) -> Option<SEXP> {
170        let rownames = unsafe { ffi::Rf_GetRowNames(self.0) };
171        if rownames.is_nil() {
172            None
173        } else {
174            Some(rownames)
175        }
176    }
177
178    /// Get column names from the `dimnames` attribute.
179    #[inline]
180    pub fn get_colnames(self) -> Option<SEXP> {
181        let dimnames = self.0.get_dimnames();
182        if dimnames.is_nil() {
183            return None;
184        }
185        let colnames = unsafe { ffi::Rf_GetColNames(dimnames) };
186        if colnames.is_nil() {
187            None
188        } else {
189            Some(colnames)
190        }
191    }
192
193    /// Get the `levels` attribute if present (for factors).
194    #[inline]
195    pub fn get_levels(self) -> Option<SEXP> {
196        self.get_attr_opt(SEXP::levels_symbol())
197    }
198
199    /// Get the `tsp` attribute if present (for time series).
200    #[inline]
201    pub fn get_tsp(self) -> Option<SEXP> {
202        self.get_attr_opt(SEXP::tsp_symbol())
203    }
204    // endregion
205
206    // region: Attribute setters (equivalent to R's SET_* macros)
207
208    /// Set the `names` attribute; returns the same list for chaining.
209    ///
210    /// Equivalent to R's `SET_NAMES(x, n)`.
211    #[inline]
212    pub fn set_names(self, names: SEXP) -> Self {
213        self.0.set_names(names);
214        self
215    }
216
217    /// Set the `class` attribute; returns the same list for chaining.
218    ///
219    /// Equivalent to R's `SET_CLASS(x, n)`.
220    #[inline]
221    pub fn set_class(self, class: SEXP) -> Self {
222        self.0.set_class(class);
223        self
224    }
225
226    /// Set the `dim` attribute; returns the same list for chaining.
227    ///
228    /// Equivalent to R's `SET_DIM(x, n)`.
229    #[inline]
230    pub fn set_dim(self, dim: SEXP) -> Self {
231        self.0.set_dim(dim);
232        self
233    }
234
235    /// Set the `dimnames` attribute; returns the same list for chaining.
236    ///
237    /// Equivalent to R's `SET_DIMNAMES(x, n)`.
238    #[inline]
239    pub fn set_dimnames(self, dimnames: SEXP) -> Self {
240        self.0.set_dimnames(dimnames);
241        self
242    }
243
244    /// Set the `levels` attribute; returns the same list for chaining.
245    ///
246    /// Equivalent to R's `SET_LEVELS(x, l)`.
247    #[inline]
248    pub fn set_levels(self, levels: SEXP) -> Self {
249        self.0.set_levels(levels);
250        self
251    }
252    // endregion
253
254    // region: Convenience setters (string-based)
255
256    /// Set the `class` attribute from a slice of class names.
257    ///
258    /// This is a convenience wrapper that creates a character vector from the
259    /// provided strings and sets it as the class attribute.
260    ///
261    /// # Example
262    ///
263    /// ```ignore
264    /// let list = List::from_pairs(vec![("x", vec![1, 2, 3])]);
265    /// let df = list.set_class_str(&["data.frame"]);
266    /// ```
267    #[inline]
268    pub fn set_class_str(self, classes: &[&str]) -> Self {
269        use crate::ffi::SEXPTYPE::STRSXP;
270
271        let n: isize = classes
272            .len()
273            .try_into()
274            .expect("classes length exceeds isize::MAX");
275        unsafe {
276            let class_vec = OwnedProtect::new(ffi::Rf_allocVector(STRSXP, n));
277            for (i, class) in classes.iter().enumerate() {
278                let idx: isize = i.try_into().expect("index exceeds isize::MAX");
279                class_vec.get().set_string_elt(idx, SEXP::charsxp(class));
280            }
281            self.0.set_class(class_vec.get());
282        }
283        self
284    }
285
286    /// Set class = `"data.frame"` using a cached class STRSXP.
287    ///
288    /// Equivalent to `set_class_str(&["data.frame"])` but avoids allocation.
289    #[inline]
290    pub fn set_data_frame_class(self) -> Self {
291        self.0
292            .set_class(crate::cached_class::data_frame_class_sexp());
293        self
294    }
295
296    /// Set the `names` attribute from a slice of strings.
297    ///
298    /// This is a convenience wrapper that creates a character vector from the
299    /// provided strings and sets it as the names attribute.
300    ///
301    /// # Example
302    ///
303    /// ```ignore
304    /// let list = List::from_values(vec![1, 2, 3]);
305    /// let named = list.set_names_str(&["a", "b", "c"]);
306    /// ```
307    #[inline]
308    pub fn set_names_str(self, names: &[&str]) -> Self {
309        use crate::ffi::SEXPTYPE::STRSXP;
310
311        let n: isize = names
312            .len()
313            .try_into()
314            .expect("names length exceeds isize::MAX");
315        unsafe {
316            let names_vec = OwnedProtect::new(ffi::Rf_allocVector(STRSXP, n));
317            for (i, name) in names.iter().enumerate() {
318                let idx: isize = i.try_into().expect("index exceeds isize::MAX");
319                names_vec.get().set_string_elt(idx, SEXP::charsxp(name));
320            }
321            self.0.set_names(names_vec.get());
322        }
323        self
324    }
325
326    /// Set `row.names` for a data.frame using compact integer form.
327    ///
328    /// R internally represents row.names as a compact integer vector
329    /// `c(NA_integer_, -n)` when the row names are just `1:n`. This is more
330    /// memory-efficient than storing n strings.
331    ///
332    /// # Example
333    ///
334    /// ```ignore
335    /// let list = List::from_pairs(vec![
336    ///     ("x", vec![1, 2, 3]),
337    ///     ("y", vec![4, 5, 6]),
338    /// ])
339    /// .set_class_str(&["data.frame"])
340    /// .set_row_names_int(3);  // Row names: "1", "2", "3"
341    /// ```
342    #[inline]
343    pub fn set_row_names_int(self, n: usize) -> Self {
344        unsafe {
345            // R's compact row.names: c(NA_integer_, -n)
346            let (row_names, rn) = crate::into_r::alloc_r_vector::<i32>(2);
347            let _guard = OwnedProtect::new(row_names);
348            rn[0] = i32::MIN; // NA_INTEGER
349            let n_i32 = i32::try_from(n).unwrap_or_else(|_| {
350                panic!("row count {n} exceeds i32::MAX");
351            });
352            rn[1] = -n_i32;
353            self.0.set_row_names(row_names);
354        }
355        self
356    }
357
358    /// Set `row.names` from a vector of strings.
359    ///
360    /// Use this when you need custom row names. For simple sequential row names
361    /// (1, 2, 3, ...), use [`set_row_names_int`](Self::set_row_names_int) instead.
362    ///
363    /// # Example
364    ///
365    /// ```ignore
366    /// let list = List::from_pairs(vec![
367    ///     ("x", vec![1, 2, 3]),
368    /// ])
369    /// .set_class_str(&["data.frame"])
370    /// .set_row_names_str(&["row_a", "row_b", "row_c"]);
371    /// ```
372    #[inline]
373    pub fn set_row_names_str(self, row_names: &[&str]) -> Self {
374        use crate::ffi::SEXPTYPE::STRSXP;
375
376        let n: isize = row_names
377            .len()
378            .try_into()
379            .expect("row_names length exceeds isize::MAX");
380        unsafe {
381            let names_vec = OwnedProtect::new(ffi::Rf_allocVector(STRSXP, n));
382            for (i, name) in row_names.iter().enumerate() {
383                let idx: isize = i.try_into().expect("index exceeds isize::MAX");
384                names_vec.get().set_string_elt(idx, SEXP::charsxp(name));
385            }
386            self.0.set_row_names(names_vec.get());
387        }
388        self
389    }
390    // endregion
391
392    // region: Safe element insertion
393
394    /// Set an element at the given index, protecting the child during insertion.
395    ///
396    /// This is the safe way to insert a freshly allocated SEXP into a list.
397    /// The child is protected for the duration of the `SET_VECTOR_ELT` call,
398    /// ensuring it cannot be garbage collected.
399    ///
400    /// # Safety
401    ///
402    /// - Must be called from the R main thread
403    /// - `child` must be a valid SEXP
404    /// - `self` must be a valid, protected VECSXP
405    ///
406    /// # Panics
407    ///
408    /// Panics if `idx` is out of bounds.
409    ///
410    /// # Example
411    ///
412    /// ```ignore
413    /// let scope = ProtectScope::new();
414    /// let list = List::from_raw(scope.alloc_vecsxp(n).into_raw());
415    ///
416    /// for i in 0..n {
417    ///     let child = Rf_allocVector(REALSXP, 10);  // unprotected!
418    ///     list.set_elt(i, child);  // safe: protects child during insertion
419    /// }
420    /// ```
421    #[inline]
422    pub unsafe fn set_elt(self, idx: isize, child: SEXP) {
423        assert!(idx >= 0 && idx < self.len(), "index out of bounds");
424        // Protect child for the duration of SET_VECTOR_ELT.
425        // Once inserted, the child is protected by the parent container.
426        // SAFETY: caller guarantees R main thread and valid SEXPs
427        unsafe {
428            let _guard = OwnedProtect::new(child);
429            self.0.set_vector_elt(idx, child);
430        }
431    }
432
433    /// Set an element without protecting the child.
434    ///
435    /// # Safety
436    ///
437    /// In addition to the safety requirements of [`set_elt`](Self::set_elt):
438    /// - The caller must ensure `child` is already protected or that no GC
439    ///   can occur between child allocation and this call.
440    ///
441    /// Use this for performance when you know the child is already protected
442    /// (e.g., it's a child of another protected container, or you have an
443    /// `OwnedProtect` guard for it).
444    #[inline]
445    pub unsafe fn set_elt_unchecked(self, idx: isize, child: SEXP) {
446        debug_assert!(idx >= 0 && idx < self.len(), "index out of bounds");
447        // SAFETY: caller guarantees child is protected and valid
448        self.0.set_vector_elt(idx, child);
449    }
450
451    /// Set an element using a callback that produces the child.
452    ///
453    /// The callback is executed within a protection scope, so any allocations
454    /// it performs are protected until insertion completes.
455    ///
456    /// # Safety
457    ///
458    /// - Must be called from the R main thread
459    /// - `self` must be a valid, protected VECSXP
460    ///
461    /// # Example
462    ///
463    /// ```ignore
464    /// let list = List::from_raw(scope.alloc_vecsxp(n).into_raw());
465    ///
466    /// for i in 0..n {
467    ///     list.set_elt_with(i, || {
468    ///         let vec = Rf_allocVector(REALSXP, 10);
469    ///         fill_vector(vec);  // can allocate internally
470    ///         vec
471    ///     });
472    /// }
473    /// ```
474    #[inline]
475    pub unsafe fn set_elt_with<F>(self, idx: isize, f: F)
476    where
477        F: FnOnce() -> SEXP,
478    {
479        assert!(idx >= 0 && idx < self.len(), "index out of bounds");
480        // SAFETY: caller guarantees R main thread
481        unsafe {
482            let child = OwnedProtect::new(f());
483            self.0.set_vector_elt(idx, child.get());
484        }
485    }
486    // endregion
487}
488
489// region: ListBuilder - efficient batch list construction
490
491use crate::gc_protect::ProtectScope;
492
493/// Builder for constructing lists with efficient protection management.
494///
495/// `ListBuilder` holds a reference to a [`ProtectScope`], allowing multiple
496/// elements to be inserted without repeatedly protecting/unprotecting each one.
497/// This is more efficient than using [`List::set_elt`] in a loop.
498///
499/// # Example
500///
501/// ```ignore
502/// unsafe fn build_list(n: isize) -> SEXP {
503///     let scope = ProtectScope::new();
504///     let builder = ListBuilder::new(&scope, n);
505///
506///     for i in 0..n {
507///         // Allocations inside the loop are protected by the scope
508///         let child = scope.alloc_real(10).into_raw();
509///         builder.set(i, child);
510///     }
511///
512///     builder.into_sexp()
513/// }
514/// ```
515pub struct ListBuilder<'a> {
516    list: SEXP,
517    _scope: &'a ProtectScope,
518}
519
520impl<'a> ListBuilder<'a> {
521    /// Create a new list builder with the given length.
522    ///
523    /// The list is allocated and protected using the provided scope.
524    ///
525    /// # Safety
526    ///
527    /// Must be called from the R main thread.
528    #[inline]
529    pub unsafe fn new(scope: &'a ProtectScope, len: usize) -> Self {
530        // SAFETY: caller guarantees R main thread
531        let list = unsafe { scope.alloc_vecsxp(len).into_raw() };
532        Self {
533            list,
534            _scope: scope,
535        }
536    }
537
538    /// Create a builder wrapping an existing protected list.
539    ///
540    /// # Safety
541    ///
542    /// - Must be called from the R main thread
543    /// - `list` must be a valid, protected VECSXP
544    #[inline]
545    pub unsafe fn from_protected(scope: &'a ProtectScope, list: SEXP) -> Self {
546        Self {
547            list,
548            _scope: scope,
549        }
550    }
551
552    /// Set an element at the given index.
553    ///
554    /// The `child` should be protected by the same scope (or a parent scope).
555    /// Use `scope.protect_raw(...)` before calling this method.
556    ///
557    /// # Safety
558    ///
559    /// - `child` must be a valid SEXP
560    /// - `child` should be protected (typically via the same scope)
561    #[inline]
562    pub unsafe fn set(&self, idx: isize, child: SEXP) {
563        // SAFETY: caller guarantees valid and protected child
564        unsafe {
565            debug_assert!(idx >= 0 && idx < ffi::Rf_xlength(self.list));
566            self.list.set_vector_elt(idx, child);
567        }
568    }
569
570    /// Set an element, protecting the child within the builder's scope.
571    ///
572    /// This is a convenience method that protects the child and then inserts it.
573    ///
574    /// # Safety
575    ///
576    /// - `child` must be a valid SEXP
577    #[inline]
578    pub unsafe fn set_protected(&self, idx: isize, child: SEXP) {
579        // SAFETY: caller guarantees valid child
580        unsafe {
581            debug_assert!(idx >= 0 && idx < ffi::Rf_xlength(self.list));
582            let _guard = OwnedProtect::new(child);
583            self.list.set_vector_elt(idx, child);
584        }
585    }
586
587    /// Get the underlying list SEXP.
588    #[inline]
589    pub fn as_sexp(&self) -> SEXP {
590        self.list
591    }
592
593    /// Convert to a `List` wrapper.
594    #[inline]
595    pub fn into_list(self) -> List {
596        List(self.list)
597    }
598
599    /// Convert to the underlying SEXP.
600    #[inline]
601    pub fn into_sexp(self) -> SEXP {
602        self.list
603    }
604
605    /// Get the length of the list.
606    #[inline]
607    pub fn len(&self) -> isize {
608        unsafe { ffi::Rf_xlength(self.list) }
609    }
610
611    /// Check if the list is empty.
612    #[inline]
613    pub fn is_empty(&self) -> bool {
614        self.len() == 0
615    }
616}
617// endregion
618
619mod accumulator;
620mod named;
621
622pub use accumulator::*;
623pub use named::*;
624
625// region: IntoList and TryFromList traits
626
627/// Convert things into an R list.
628pub trait IntoList {
629    /// Convert `self` into an R list wrapper.
630    fn into_list(self) -> List;
631}
632
633/// Fallible conversion from an R list into a Rust value.
634pub trait TryFromList: Sized {
635    /// Error returned when conversion fails.
636    type Error;
637
638    /// Attempt to convert an R list wrapper into `Self`.
639    fn try_from_list(list: List) -> Result<Self, Self::Error>;
640}
641
642impl<T: IntoR> IntoList for Vec<T> {
643    fn into_list(self) -> List {
644        let converted: Vec<SEXP> = self.into_iter().map(|v| v.into_sexp()).collect();
645        let n: isize = converted
646            .len()
647            .try_into()
648            .expect("list length exceeds isize::MAX");
649        unsafe {
650            let list = ffi::Rf_allocVector(VECSXP, n);
651            for (i, val) in converted.into_iter().enumerate() {
652                let idx: isize = i.try_into().expect("index exceeds isize::MAX");
653                list.set_vector_elt(idx, val);
654            }
655            List(list)
656        }
657    }
658}
659
660impl<T> TryFromList for Vec<T>
661where
662    T: TryFromSexp<Error = SexpError>,
663{
664    type Error = SexpError;
665
666    fn try_from_list(list: List) -> Result<Self, Self::Error> {
667        let expected: usize = list
668            .len()
669            .try_into()
670            .expect("list length must be non-negative");
671        let mut out = Vec::with_capacity(expected);
672        for i in 0..expected {
673            let idx: isize = i.try_into().expect("index exceeds isize::MAX");
674            let sexp = list.get(idx).ok_or_else(|| {
675                SexpError::from(SexpLengthError {
676                    expected,
677                    actual: i,
678                })
679            })?;
680            out.push(TryFromSexp::try_from_sexp(sexp)?);
681        }
682        Ok(out)
683    }
684}
685
686// endregion
687
688// region: HashMap conversions
689
690impl<K, V> IntoList for HashMap<K, V>
691where
692    K: AsRef<str>,
693    V: IntoR,
694{
695    fn into_list(self) -> List {
696        let pairs: Vec<(K, V)> = self.into_iter().collect();
697        List::from_pairs(pairs)
698    }
699}
700
701impl<V> TryFromList for HashMap<String, V>
702where
703    V: TryFromSexp<Error = SexpError>,
704{
705    type Error = SexpError;
706
707    fn try_from_list(list: List) -> Result<Self, Self::Error> {
708        let n: usize = list
709            .len()
710            .try_into()
711            .expect("list length must be non-negative");
712        let names_sexp = list.names();
713        let mut map = HashMap::with_capacity(n);
714
715        for i in 0..n {
716            let idx: isize = i.try_into().expect("index exceeds isize::MAX");
717            let sexp = list.get(idx).ok_or_else(|| {
718                SexpError::from(SexpLengthError {
719                    expected: n,
720                    actual: i,
721                })
722            })?;
723            let value: V = TryFromSexp::try_from_sexp(sexp)?;
724
725            let key = if let Some(names) = names_sexp {
726                let name_sexp = names.string_elt(idx);
727                if name_sexp == SEXP::na_string() {
728                    format!("{i}")
729                } else {
730                    let name_ptr = name_sexp.r_char();
731                    let name_cstr = unsafe { std::ffi::CStr::from_ptr(name_ptr) };
732                    name_cstr.to_str().unwrap_or(&format!("{i}")).to_string()
733                }
734            } else {
735                format!("{i}")
736            };
737
738            map.insert(key, value);
739        }
740        Ok(map)
741    }
742}
743// endregion
744
745// region: BTreeMap conversions
746
747impl<K, V> IntoList for BTreeMap<K, V>
748where
749    K: AsRef<str>,
750    V: IntoR,
751{
752    fn into_list(self) -> List {
753        let pairs: Vec<(K, V)> = self.into_iter().collect();
754        List::from_pairs(pairs)
755    }
756}
757
758impl<V> TryFromList for BTreeMap<String, V>
759where
760    V: TryFromSexp<Error = SexpError>,
761{
762    type Error = SexpError;
763
764    fn try_from_list(list: List) -> Result<Self, Self::Error> {
765        let n: usize = list
766            .len()
767            .try_into()
768            .expect("list length must be non-negative");
769        let names_sexp = list.names();
770        let mut map = BTreeMap::new();
771
772        for i in 0..n {
773            let idx: isize = i.try_into().expect("index exceeds isize::MAX");
774            let sexp = list.get(idx).ok_or_else(|| {
775                SexpError::from(SexpLengthError {
776                    expected: n,
777                    actual: i,
778                })
779            })?;
780            let value: V = TryFromSexp::try_from_sexp(sexp)?;
781
782            let key = if let Some(names) = names_sexp {
783                let name_sexp = names.string_elt(idx);
784                if name_sexp == SEXP::na_string() {
785                    format!("{i}")
786                } else {
787                    let name_ptr = name_sexp.r_char();
788                    let name_cstr = unsafe { std::ffi::CStr::from_ptr(name_ptr) };
789                    name_cstr.to_str().unwrap_or(&format!("{i}")).to_string()
790                }
791            } else {
792                format!("{i}")
793            };
794
795            map.insert(key, value);
796        }
797        Ok(map)
798    }
799}
800// endregion
801
802// region: HashSet conversions (unnamed list <-> set)
803
804impl<T> IntoList for HashSet<T>
805where
806    T: IntoR,
807{
808    fn into_list(self) -> List {
809        let values: Vec<T> = self.into_iter().collect();
810        values.into_list()
811    }
812}
813
814impl<T> TryFromList for HashSet<T>
815where
816    T: TryFromSexp<Error = SexpError> + Eq + Hash,
817{
818    type Error = SexpError;
819
820    fn try_from_list(list: List) -> Result<Self, Self::Error> {
821        let vec: Vec<T> = TryFromList::try_from_list(list)?;
822        Ok(vec.into_iter().collect())
823    }
824}
825// endregion
826
827// region: BTreeSet conversions (unnamed list <-> set)
828
829impl<T> IntoList for BTreeSet<T>
830where
831    T: IntoR,
832{
833    fn into_list(self) -> List {
834        let values: Vec<T> = self.into_iter().collect();
835        values.into_list()
836    }
837}
838
839impl<T> TryFromList for BTreeSet<T>
840where
841    T: TryFromSexp<Error = SexpError> + Ord,
842{
843    type Error = SexpError;
844
845    fn try_from_list(list: List) -> Result<Self, Self::Error> {
846        let vec: Vec<T> = TryFromList::try_from_list(list)?;
847        Ok(vec.into_iter().collect())
848    }
849}
850
851impl List {
852    /// Build a list from `(name, value)` pairs, setting `names` in one pass.
853    pub fn from_pairs<N, T>(pairs: Vec<(N, T)>) -> Self
854    where
855        N: AsRef<str>,
856        T: IntoR,
857    {
858        let raw: Vec<(N, SEXP)> = pairs.into_iter().map(|(n, v)| (n, v.into_sexp())).collect();
859        Self::from_raw_pairs(raw)
860    }
861
862    /// Build an unnamed list from values.
863    ///
864    /// Use this for tuple-like structures where positional access is more natural.
865    ///
866    /// # Example
867    ///
868    /// ```ignore
869    /// let list = List::from_values(vec![1i32, 2i32, 3i32]);
870    /// // R: list(1L, 2L, 3L) - accessed as [[1]], [[2]], [[3]]
871    /// ```
872    pub fn from_values<T: IntoR>(values: Vec<T>) -> Self {
873        values.into_list()
874    }
875
876    /// Build an unnamed list from pre-converted SEXPs.
877    ///
878    /// # Safety Note
879    ///
880    /// The input SEXPs should already be protected or be children of protected
881    /// containers. This function protects the list during construction.
882    pub fn from_raw_values(values: Vec<SEXP>) -> Self {
883        let n: isize = values
884            .len()
885            .try_into()
886            .expect("values length exceeds isize::MAX");
887        unsafe {
888            // Protect list during construction. SET_VECTOR_ELT doesn't allocate,
889            // but we protect defensively in case this code is modified later.
890            let list = OwnedProtect::new(ffi::Rf_allocVector(VECSXP, n));
891            for (i, val) in values.into_iter().enumerate() {
892                let idx: isize = i.try_into().expect("index exceeds isize::MAX");
893                list.get().set_vector_elt(idx, val);
894            }
895            List(list.get())
896        }
897    }
898
899    /// Build an atomic vector from homogeneous length-1 scalar SEXPs.
900    ///
901    /// If all elements are length-1 scalars of the same coalesceable type
902    /// (INTSXP, REALSXP, LGLSXP, STRSXP), returns that atomic vector.
903    /// Otherwise returns a VECSXP (generic list).
904    ///
905    /// This is the canonical entry point for both `DataFrame::into_data_frame`
906    /// (column building) and `SeqSerializer::end` (sequence coalescing).
907    ///
908    /// # Safety Note
909    ///
910    /// The input SEXPs should already be protected or be children of protected
911    /// containers.
912    pub fn from_scalars_or_list(elements: &[SEXP]) -> Self {
913        use crate::ffi::SEXPTYPE;
914        use crate::into_r::alloc_r_vector;
915
916        if elements.is_empty() {
917            return Self::from_raw_values(Vec::new());
918        }
919
920        let first_type = elements[0].type_of();
921        let all_scalar_same_type = elements
922            .iter()
923            .all(|&e| unsafe { ffi::Rf_xlength(e) == 1 && e.type_of() == first_type });
924
925        if !all_scalar_same_type {
926            return Self::from_raw_values(elements.to_vec());
927        }
928
929        let n = elements.len();
930        let sexp = match first_type {
931            // For native types: allocate R vector, get mutable slice, read source
932            // scalars via as_slice()[0] — no per-element FFI calls.
933            SEXPTYPE::INTSXP => unsafe {
934                let (v, dst) = alloc_r_vector::<i32>(n);
935                for (slot, &elem) in dst.iter_mut().zip(elements.iter()) {
936                    *slot = *elem.as_slice::<i32>().first().expect("scalar has length 1");
937                }
938                v
939            },
940            SEXPTYPE::REALSXP => unsafe {
941                let (v, dst) = alloc_r_vector::<f64>(n);
942                for (slot, &elem) in dst.iter_mut().zip(elements.iter()) {
943                    *slot = *elem.as_slice::<f64>().first().expect("scalar has length 1");
944                }
945                v
946            },
947            SEXPTYPE::LGLSXP => unsafe {
948                let (v, dst) = alloc_r_vector::<crate::ffi::RLogical>(n);
949                for (slot, &elem) in dst.iter_mut().zip(elements.iter()) {
950                    *slot = *elem
951                        .as_slice::<crate::ffi::RLogical>()
952                        .first()
953                        .expect("scalar has length 1");
954                }
955                v
956            },
957            // STRSXP elements are CHARSXPs — must use SET_STRING_ELT (no slice access).
958            SEXPTYPE::STRSXP => unsafe {
959                let v = OwnedProtect::new(ffi::Rf_allocVector(SEXPTYPE::STRSXP, n as isize));
960                for (i, &elem) in elements.iter().enumerate() {
961                    let idx: isize = i.try_into().expect("index exceeds isize::MAX");
962                    v.get().set_string_elt(idx, elem.string_elt(0));
963                }
964                v.get()
965            },
966            _ => return Self::from_raw_values(elements.to_vec()),
967        };
968        List(sexp)
969    }
970
971    /// Build a list from `(name, SEXP)` pairs (heterogeneous-friendly).
972    ///
973    /// # Safety Note
974    ///
975    /// The input SEXPs should already be protected or be children of protected
976    /// containers. This function protects the list and names vector during
977    /// construction.
978    pub fn from_raw_pairs<N>(pairs: Vec<(N, SEXP)>) -> Self
979    where
980        N: AsRef<str>,
981    {
982        let n: isize = pairs
983            .len()
984            .try_into()
985            .expect("pairs length exceeds isize::MAX");
986        unsafe {
987            // CRITICAL: Both list and names must be protected because
988            // Rf_mkCharLenCE can allocate and trigger GC in the loop below.
989            let list = OwnedProtect::new(ffi::Rf_allocVector(VECSXP, n));
990            let names = OwnedProtect::new(ffi::Rf_allocVector(STRSXP, n));
991            for (i, (name, val)) in pairs.into_iter().enumerate() {
992                let idx: isize = i.try_into().expect("index exceeds isize::MAX");
993                list.get().set_vector_elt(idx, val);
994
995                let s = name.as_ref();
996                // SEXP::charsxp allocates - list and names must be protected!
997                names.get().set_string_elt(idx, SEXP::charsxp(s));
998            }
999            list.get().set_names(names.get());
1000            List(list.get())
1001        }
1002    }
1003}
1004
1005impl IntoR for List {
1006    type Error = std::convert::Infallible;
1007    fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
1008        Ok(self.into_sexp())
1009    }
1010    unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
1011        self.try_into_sexp()
1012    }
1013    #[inline]
1014    fn into_sexp(self) -> SEXP {
1015        self.0
1016    }
1017}
1018
1019impl IntoR for ListMut {
1020    type Error = std::convert::Infallible;
1021    fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
1022        Ok(self.into_sexp())
1023    }
1024    unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
1025        self.try_into_sexp()
1026    }
1027    #[inline]
1028    fn into_sexp(self) -> SEXP {
1029        self.0
1030    }
1031}
1032
1033/// Error when a list has duplicate non-NA names.
1034#[derive(Debug, Clone)]
1035pub struct DuplicateNameError {
1036    /// The duplicate name that was found.
1037    pub name: String,
1038}
1039
1040impl std::fmt::Display for DuplicateNameError {
1041    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1042        write!(f, "list has duplicate name: {:?}", self.name)
1043    }
1044}
1045
1046impl std::error::Error for DuplicateNameError {}
1047
1048/// Error when converting SEXP to List fails.
1049#[derive(Debug, Clone)]
1050pub enum ListFromSexpError {
1051    /// Wrong SEXP type.
1052    Type(crate::from_r::SexpTypeError),
1053    /// Duplicate non-NA name found.
1054    DuplicateName(DuplicateNameError),
1055}
1056
1057impl std::fmt::Display for ListFromSexpError {
1058    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1059        match self {
1060            ListFromSexpError::Type(e) => write!(f, "{}", e),
1061            ListFromSexpError::DuplicateName(e) => write!(f, "{}", e),
1062        }
1063    }
1064}
1065
1066impl std::error::Error for ListFromSexpError {}
1067
1068impl From<crate::from_r::SexpTypeError> for ListFromSexpError {
1069    fn from(e: crate::from_r::SexpTypeError) -> Self {
1070        ListFromSexpError::Type(e)
1071    }
1072}
1073
1074impl TryFromSexp for List {
1075    type Error = ListFromSexpError;
1076
1077    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1078        let actual = sexp.type_of();
1079
1080        // Accept VECSXP (generic list) directly
1081        // Also accept LISTSXP (pairlist) by coercing to VECSXP
1082        // Note: Rf_isList() only returns true for LISTSXP/NILSXP, not VECSXP
1083        let list_sexp = if actual == VECSXP {
1084            sexp
1085        } else if actual == LISTSXP {
1086            // Accept pairlists by coercing to a VECSXP list.
1087            sexp.coerce(VECSXP)
1088        } else {
1089            return Err(crate::from_r::SexpTypeError {
1090                expected: VECSXP,
1091                actual,
1092            }
1093            .into());
1094        };
1095
1096        // Check for duplicate non-NA names
1097        let names_sexp = list_sexp.get_names();
1098        if names_sexp != SEXP::nil() {
1099            let n = unsafe { ffi::Rf_xlength(list_sexp) };
1100            let n_usize: usize = n.try_into().expect("list length must be non-negative");
1101            let mut seen = HashSet::with_capacity(n_usize);
1102
1103            for i in 0..n {
1104                let name_sexp = names_sexp.string_elt(i);
1105                // Skip NA names
1106                if name_sexp == SEXP::na_string() {
1107                    continue;
1108                }
1109                // Skip empty names
1110                let name_ptr = name_sexp.r_char();
1111                let name_cstr = unsafe { std::ffi::CStr::from_ptr(name_ptr) };
1112                if let Ok(s) = name_cstr.to_str() {
1113                    if s.is_empty() {
1114                        continue;
1115                    }
1116                    if !seen.insert(s) {
1117                        return Err(ListFromSexpError::DuplicateName(DuplicateNameError {
1118                            name: s.to_string(),
1119                        }));
1120                    }
1121                }
1122            }
1123        }
1124
1125        Ok(List(list_sexp))
1126    }
1127}
1128
1129impl TryFromSexp for Option<List> {
1130    type Error = SexpError;
1131
1132    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1133        if sexp == SEXP::nil() {
1134            return Ok(None);
1135        }
1136        let list = List::try_from_sexp(sexp).map_err(|e| SexpError::InvalidValue(e.to_string()))?;
1137        Ok(Some(list))
1138    }
1139}
1140
1141impl TryFromSexp for Option<ListMut> {
1142    type Error = SexpError;
1143
1144    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1145        if sexp == SEXP::nil() {
1146            return Ok(None);
1147        }
1148        let list = ListMut::try_from_sexp(sexp)?;
1149        Ok(Some(list))
1150    }
1151}
1152
1153impl TryFromSexp for ListMut {
1154    type Error = SexpError;
1155
1156    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1157        let actual = sexp.type_of();
1158        if actual != VECSXP {
1159            return Err(SexpTypeError {
1160                expected: VECSXP,
1161                actual,
1162            }
1163            .into());
1164        }
1165        Ok(ListMut(sexp))
1166    }
1167}
1168// endregion