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