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