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