Skip to main content

miniextendr_api/
from_r.rs

1#![allow(rustdoc::private_intra_doc_links)]
2//! Conversions from R SEXP to Rust types.
3//!
4//! This module provides [`TryFromSexp`] implementations for converting R values to Rust types:
5//!
6//! | R Type | Rust Type | Access Method |
7//! |--------|-----------|---------------|
8//! | INTSXP | `i32`, `&[i32]` | `INTEGER()` / `DATAPTR_RO` |
9//! | REALSXP | `f64`, `&[f64]` | `REAL()` / `DATAPTR_RO` |
10//! | LGLSXP | `RLogical`, `&[RLogical]` | `LOGICAL()` / `DATAPTR_RO` |
11//! | RAWSXP | `u8`, `&[u8]` | `RAW()` / `DATAPTR_RO` |
12//! | CPLXSXP | `Rcomplex` | `COMPLEX()` / `DATAPTR_RO` |
13//! | STRSXP | `&str`, `String` | `)` + `R_CHAR()` / `Rf_translateCharUTF8()` |
14//!
15//! # Submodules
16//!
17//! | Module | Contents |
18//! |--------|----------|
19//! | [`logical`] | `Rboolean`.string_elt(`bool`, `Option<bool>` |
20//! | [`coerced_scalars`] | Multi-source numeric scalars (`i8`..`usize`) + large integers (`i64`, `u64`) |
21//! | [`references`] | Borrowed views: `&T`, `&mut T`, `&[T]`, `Vec<&T>` |
22//! | [`strings`] | `&str`, `String`, `char` from STRSXP |
23//! | [`na_vectors`] | `Vec<Option<T>>`, `Box<[Option<T>]>` with NA awareness |
24//! | [`collections`] | `HashMap`, `BTreeMap`, `HashSet`, `BTreeSet` |
25//! | [`cow_and_paths`] | `Cow<[T]>`, `PathBuf`, `OsString`, string sets |
26//!
27//! # Thread Safety
28//!
29//! The trait provides two methods:
30//! - [`TryFromSexp::try_from_sexp`] - checked version with debug thread assertions
31//! - [`TryFromSexp::try_from_sexp_unchecked`] - unchecked version for performance-critical paths
32//!
33//! Use `try_from_sexp_unchecked` when you're certain you're on the main thread:
34//! - Inside ALTREP callbacks
35//! - Inside `#[miniextendr(unsafe(main_thread))]` functions
36//! - Inside `extern "C-unwind"` functions called directly by R
37
38use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
39
40use crate::altrep_traits::NA_REAL;
41use crate::coerce::TryCoerce;
42use crate::ffi::{RLogical, SEXP, SEXPTYPE, SexpExt};
43
44/// Check if an f64 value is R's NA_real_ (a specific NaN bit pattern).
45///
46/// This is different from `f64::is_nan()` which returns true for ALL NaN values.
47/// R's `NA_real_` is a specific NaN with a particular bit pattern, while regular
48/// NaN values (e.g., from `0.0/0.0`) should be preserved as valid values.
49#[inline]
50pub(crate) fn is_na_real(value: f64) -> bool {
51    value.to_bits() == NA_REAL.to_bits()
52}
53
54// region: CHARSXP to string conversion
55
56/// Convert CHARSXP to `&str` — zero-copy from R's string data.
57///
58/// Uses `R_CHAR` + `LENGTH` (O(1), no strlen). UTF-8 validity is guaranteed
59/// by `miniextendr_assert_utf8_locale()` at package init, so no per-string
60/// validation is needed.
61///
62/// # Safety
63///
64/// - `charsxp` must be a valid CHARSXP (not NA_STRING, not null).
65/// - The returned `&str` is only valid as long as R doesn't GC the CHARSXP.
66#[inline]
67pub(crate) unsafe fn charsxp_to_str(charsxp: SEXP) -> &'static str {
68    unsafe { charsxp_to_str_impl(charsxp.r_char(), charsxp) }
69}
70
71/// Unchecked version of [`charsxp_to_str`] (skips R thread checks on `R_CHAR`).
72#[inline]
73pub(crate) unsafe fn charsxp_to_str_unchecked(charsxp: SEXP) -> &'static str {
74    unsafe { charsxp_to_str_impl(charsxp.r_char_unchecked(), charsxp) }
75}
76
77/// Shared implementation: given a data pointer and CHARSXP, produce `&str`.
78///
79/// UTF-8 locale is asserted at init — `from_utf8_unchecked` is safe.
80#[inline]
81unsafe fn charsxp_to_str_impl(ptr: *const std::os::raw::c_char, charsxp: SEXP) -> &'static str {
82    unsafe {
83        let len: usize = charsxp.len();
84        let bytes = r_slice(ptr.cast::<u8>(), len);
85        // SAFETY: miniextendr_assert_utf8_locale() at init guarantees all
86        // CHARSXPs in this session are valid UTF-8 or ASCII.
87        debug_assert!(
88            std::str::from_utf8(bytes).is_ok(),
89            "CHARSXP contains non-UTF-8 bytes (locale assertion may have been skipped)"
90        );
91        std::str::from_utf8_unchecked(bytes)
92    }
93}
94
95/// `charsxp_to_cow` is now just an alias — all CHARSXPs are UTF-8 (asserted
96/// at init), so there's no non-UTF-8 fallback path. Returns `Cow::Borrowed`.
97#[inline]
98pub(crate) unsafe fn charsxp_to_cow(charsxp: SEXP) -> std::borrow::Cow<'static, str> {
99    std::borrow::Cow::Borrowed(unsafe { charsxp_to_str(charsxp) })
100}
101
102/// Create a slice from an R data pointer, handling the zero-length case.
103///
104/// R returns a sentinel pointer (`0x1`) instead of null for empty vectors
105/// (e.g., `LOGICAL(integer(0))` → `0x1`). Rust 1.93+ validates pointer
106/// alignment in `slice::from_raw_parts` even for `len == 0`, so passing
107/// R's sentinel directly causes a precondition-check abort.
108///
109/// This helper returns an empty slice for `len == 0` without touching the pointer.
110///
111/// # Safety
112///
113/// If `len > 0`, `ptr` must satisfy the requirements of [`std::slice::from_raw_parts`].
114#[inline(always)]
115pub(crate) unsafe fn r_slice<'a, T>(ptr: *const T, len: usize) -> &'a [T] {
116    if len == 0 {
117        &[]
118    } else {
119        unsafe { std::slice::from_raw_parts(ptr, len) }
120    }
121}
122
123/// Mutable version of [`r_slice`] for `from_raw_parts_mut`.
124///
125/// # Safety
126///
127/// If `len > 0`, `ptr` must satisfy the requirements of [`std::slice::from_raw_parts_mut`].
128#[inline(always)]
129pub(crate) unsafe fn r_slice_mut<'a, T>(ptr: *mut T, len: usize) -> &'a mut [T] {
130    if len == 0 {
131        &mut []
132    } else {
133        unsafe { std::slice::from_raw_parts_mut(ptr, len) }
134    }
135}
136
137#[derive(Debug, Clone, Copy)]
138/// Error describing an unexpected R `SEXPTYPE`.
139pub struct SexpTypeError {
140    /// Expected R type.
141    pub expected: SEXPTYPE,
142    /// Actual R type encountered.
143    pub actual: SEXPTYPE,
144}
145
146impl std::fmt::Display for SexpTypeError {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        write!(
149            f,
150            "type mismatch: expected {:?}, got {:?}",
151            self.expected, self.actual
152        )
153    }
154}
155
156impl std::error::Error for SexpTypeError {}
157
158#[derive(Debug, Clone, Copy)]
159/// Error describing an unexpected R object length.
160pub struct SexpLengthError {
161    /// Required length.
162    pub expected: usize,
163    /// Actual length encountered.
164    pub actual: usize,
165}
166
167impl std::fmt::Display for SexpLengthError {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        write!(
170            f,
171            "length mismatch: expected {}, got {}",
172            self.expected, self.actual
173        )
174    }
175}
176
177impl std::error::Error for SexpLengthError {}
178
179#[derive(Debug, Clone, Copy)]
180/// Error for NA values in conversions that require non-missing values.
181pub struct SexpNaError {
182    /// R type where an NA was found.
183    pub sexp_type: SEXPTYPE,
184}
185
186impl std::fmt::Display for SexpNaError {
187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188        write!(f, "unexpected NA value in {:?}", self.sexp_type)
189    }
190}
191
192impl std::error::Error for SexpNaError {}
193
194#[derive(Debug, Clone)]
195/// Unified conversion error when decoding an R `SEXP`.
196pub enum SexpError {
197    /// `SEXPTYPE` did not match the expected one.
198    Type(SexpTypeError),
199    /// Length did not match the expected one.
200    Length(SexpLengthError),
201    /// Missing value encountered where disallowed.
202    Na(SexpNaError),
203    /// Value is syntactically valid but semantically invalid (e.g. parse error).
204    InvalidValue(String),
205    /// A required field was missing from a named list.
206    MissingField(String),
207    /// A named list has duplicate non-empty names.
208    DuplicateName(String),
209    /// Failed to convert to `Either<L, R>` - both branches failed.
210    ///
211    /// Contains the error messages from attempting both conversions.
212    #[cfg(feature = "either")]
213    EitherConversion {
214        /// Error from attempting to convert to the Left type
215        left_error: String,
216        /// Error from attempting to convert to the Right type
217        right_error: String,
218    },
219}
220
221impl std::fmt::Display for SexpError {
222    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223        match self {
224            SexpError::Type(e) => write!(f, "{}", e),
225            SexpError::Length(e) => write!(f, "{}", e),
226            SexpError::Na(e) => write!(f, "{}", e),
227            SexpError::InvalidValue(msg) => write!(f, "invalid value: {}", msg),
228            SexpError::MissingField(name) => write!(f, "missing field: {}", name),
229            SexpError::DuplicateName(name) => write!(f, "duplicate name in list: {:?}", name),
230            #[cfg(feature = "either")]
231            SexpError::EitherConversion {
232                left_error,
233                right_error,
234            } => write!(
235                f,
236                "failed to convert to Either: Left failed ({}), Right failed ({})",
237                left_error, right_error
238            ),
239        }
240    }
241}
242
243impl std::error::Error for SexpError {
244    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
245        match self {
246            SexpError::Type(e) => Some(e),
247            SexpError::Length(e) => Some(e),
248            SexpError::Na(e) => Some(e),
249            SexpError::InvalidValue(_) => None,
250            SexpError::MissingField(_) => None,
251            SexpError::DuplicateName(_) => None,
252            #[cfg(feature = "either")]
253            SexpError::EitherConversion { .. } => None,
254        }
255    }
256}
257
258impl From<SexpTypeError> for SexpError {
259    fn from(e: SexpTypeError) -> Self {
260        SexpError::Type(e)
261    }
262}
263
264impl From<SexpLengthError> for SexpError {
265    fn from(e: SexpLengthError) -> Self {
266        SexpError::Length(e)
267    }
268}
269
270impl From<SexpNaError> for SexpError {
271    fn from(e: SexpNaError) -> Self {
272        SexpError::Na(e)
273    }
274}
275
276/// TryFrom-style trait for converting SEXP to Rust types.
277///
278/// # Examples
279///
280/// ```no_run
281/// use miniextendr_api::ffi::SEXP;
282/// use miniextendr_api::from_r::TryFromSexp;
283///
284/// fn example(sexp: SEXP) {
285///     let value: i32 = TryFromSexp::try_from_sexp(sexp).unwrap();
286///     let text: String = TryFromSexp::try_from_sexp(sexp).unwrap();
287/// }
288/// ```
289pub trait TryFromSexp: Sized {
290    /// The error type returned when conversion fails.
291    type Error;
292
293    /// Attempt to convert an R SEXP to this Rust type.
294    ///
295    /// In debug builds, may assert that we're on R's main thread.
296    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error>;
297
298    /// Convert from SEXP without thread safety checks.
299    ///
300    /// # Safety
301    ///
302    /// Must be called from R's main thread. In debug builds, this still
303    /// calls the checked version by default, but implementations may
304    /// skip thread assertions for performance.
305    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
306        // Default: just call the checked version
307        Self::try_from_sexp(sexp)
308    }
309}
310
311macro_rules! impl_try_from_sexp_scalar_native {
312    ($t:ty, $sexptype:ident) => {
313        impl TryFromSexp for $t {
314            type Error = SexpError;
315
316            #[inline]
317            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
318                let actual = sexp.type_of();
319                if actual != SEXPTYPE::$sexptype {
320                    return Err(SexpTypeError {
321                        expected: SEXPTYPE::$sexptype,
322                        actual,
323                    }
324                    .into());
325                }
326                let len = sexp.len();
327                if len != 1 {
328                    return Err(SexpLengthError {
329                        expected: 1,
330                        actual: len,
331                    }
332                    .into());
333                }
334                unsafe { sexp.as_slice::<$t>() }
335                    .first()
336                    .cloned()
337                    .ok_or_else(|| {
338                        SexpLengthError {
339                            expected: 1,
340                            actual: 0,
341                        }
342                        .into()
343                    })
344            }
345
346            #[inline]
347            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
348                let actual = sexp.type_of();
349                if actual != SEXPTYPE::$sexptype {
350                    return Err(SexpTypeError {
351                        expected: SEXPTYPE::$sexptype,
352                        actual,
353                    }
354                    .into());
355                }
356                let len = unsafe { sexp.len_unchecked() };
357                if len != 1 {
358                    return Err(SexpLengthError {
359                        expected: 1,
360                        actual: len,
361                    }
362                    .into());
363                }
364                unsafe { sexp.as_slice_unchecked::<$t>() }
365                    .first()
366                    .cloned()
367                    .ok_or_else(|| {
368                        SexpLengthError {
369                            expected: 1,
370                            actual: 0,
371                        }
372                        .into()
373                    })
374            }
375        }
376    };
377}
378
379impl_try_from_sexp_scalar_native!(i32, INTSXP);
380impl_try_from_sexp_scalar_native!(f64, REALSXP);
381impl_try_from_sexp_scalar_native!(u8, RAWSXP);
382impl_try_from_sexp_scalar_native!(RLogical, LGLSXP);
383impl_try_from_sexp_scalar_native!(crate::ffi::Rcomplex, CPLXSXP);
384
385/// Pass-through conversion for raw SEXP values with ALTREP auto-materialization.
386///
387/// This allows `SEXP` to be used directly in `#[miniextendr]` function signatures.
388/// When R passes an ALTREP vector (e.g., `1:10`, `seq_len(N)`),
389/// [`ensure_materialized`](crate::altrep_sexp::ensure_materialized) is called
390/// automatically to force materialization on the R main thread. After this,
391/// the SEXP's data pointer is stable and safe to access from any thread.
392///
393/// # ALTREP handling
394///
395/// | Input | Result |
396/// |---|---|
397/// | Regular SEXP | Passed through unchanged |
398/// | ALTREP SEXP | Materialized via `ensure_materialized`, then passed through |
399///
400/// To receive ALTREP without materializing, use
401/// [`AltrepSexp`](crate::altrep_sexp::AltrepSexp) as the parameter type instead.
402/// To receive the raw SEXP without any conversion (including no materialization),
403/// use `extern "C-unwind"`.
404///
405/// See `docs/ALTREP_SEXP.md` for the full guide.
406///
407/// # Safety
408///
409/// SEXP handles are only valid on R's main thread. Use with
410/// `#[miniextendr(unsafe(main_thread))]` functions.
411impl TryFromSexp for SEXP {
412    type Error = SexpError;
413
414    /// Converts a SEXP, auto-materializing ALTREP vectors.
415    ///
416    /// If the input is ALTREP, [`ensure_materialized`](crate::altrep_sexp::ensure_materialized)
417    /// is called to force materialization on the R main thread. After
418    /// materialization the data pointer is stable and the SEXP can be safely
419    /// sent to other threads.
420    #[inline]
421    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
422        Ok(unsafe { crate::altrep_sexp::ensure_materialized(sexp) })
423    }
424
425    #[inline]
426    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
427        Ok(unsafe { crate::altrep_sexp::ensure_materialized(sexp) })
428    }
429}
430
431impl TryFromSexp for Option<SEXP> {
432    type Error = SexpError;
433
434    #[inline]
435    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
436        if sexp.type_of() == SEXPTYPE::NILSXP {
437            Ok(None)
438        } else {
439            Ok(Some(unsafe {
440                crate::altrep_sexp::ensure_materialized(sexp)
441            }))
442        }
443    }
444
445    #[inline]
446    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
447        Self::try_from_sexp(sexp)
448    }
449}
450// endregion
451
452mod logical;
453
454mod coerced_scalars;
455pub(crate) use coerced_scalars::coerce_value;
456
457mod references;
458
459// region: Blanket implementations for slices with arbitrary lifetimes
460
461/// Blanket impl for `&[T]` where T: RNativeType
462///
463/// This replaces the macro-generated `&'static [T]` impls with a more composable
464/// blanket impl that works for any lifetime. This enables containers like TinyVec
465/// to use blanket impls without needing helper functions.
466impl<T> TryFromSexp for &[T]
467where
468    T: crate::ffi::RNativeType + Copy,
469{
470    type Error = SexpTypeError;
471
472    #[inline]
473    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
474        let actual = sexp.type_of();
475        if actual != T::SEXP_TYPE {
476            return Err(SexpTypeError {
477                expected: T::SEXP_TYPE,
478                actual,
479            });
480        }
481        Ok(unsafe { sexp.as_slice::<T>() })
482    }
483
484    #[inline]
485    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
486        let actual = sexp.type_of();
487        if actual != T::SEXP_TYPE {
488            return Err(SexpTypeError {
489                expected: T::SEXP_TYPE,
490                actual,
491            });
492        }
493        Ok(unsafe { sexp.as_slice_unchecked::<T>() })
494    }
495}
496
497/// Blanket impl for `&mut [T]` where T: RNativeType
498///
499/// # Safety note (aliasing)
500///
501/// This impl can produce aliased `&mut` slices if the same R vector is passed
502/// to multiple mutable slice parameters. The caller is responsible for ensuring
503/// no two `&mut` borrows alias the same SEXP.
504impl<T> TryFromSexp for &mut [T]
505where
506    T: crate::ffi::RNativeType + Copy,
507{
508    type Error = SexpTypeError;
509
510    #[inline]
511    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
512        let actual = sexp.type_of();
513        if actual != T::SEXP_TYPE {
514            return Err(SexpTypeError {
515                expected: T::SEXP_TYPE,
516                actual,
517            });
518        }
519        let len = sexp.len();
520        let ptr = unsafe { T::dataptr_mut(sexp) };
521        Ok(unsafe { r_slice_mut(ptr, len) })
522    }
523
524    #[inline]
525    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
526        let actual = sexp.type_of();
527        if actual != T::SEXP_TYPE {
528            return Err(SexpTypeError {
529                expected: T::SEXP_TYPE,
530                actual,
531            });
532        }
533        let len = unsafe { sexp.len_unchecked() };
534        let ptr = unsafe { T::dataptr_mut(sexp) };
535        Ok(unsafe { r_slice_mut(ptr, len) })
536    }
537}
538
539/// Blanket impl for `Option<&[T]>` where T: RNativeType
540impl<T> TryFromSexp for Option<&[T]>
541where
542    T: crate::ffi::RNativeType + Copy,
543{
544    type Error = SexpError;
545
546    #[inline]
547    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
548        if sexp.type_of() == SEXPTYPE::NILSXP {
549            return Ok(None);
550        }
551        let slice: &[T] = TryFromSexp::try_from_sexp(sexp).map_err(SexpError::from)?;
552        Ok(Some(slice))
553    }
554
555    #[inline]
556    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
557        if sexp.type_of() == SEXPTYPE::NILSXP {
558            return Ok(None);
559        }
560        let slice: &[T] =
561            unsafe { TryFromSexp::try_from_sexp_unchecked(sexp).map_err(SexpError::from)? };
562        Ok(Some(slice))
563    }
564}
565
566/// Blanket impl for `Option<&mut [T]>` where T: RNativeType
567impl<T> TryFromSexp for Option<&mut [T]>
568where
569    T: crate::ffi::RNativeType + Copy,
570{
571    type Error = SexpError;
572
573    #[inline]
574    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
575        if sexp.type_of() == SEXPTYPE::NILSXP {
576            return Ok(None);
577        }
578        let slice: &mut [T] = TryFromSexp::try_from_sexp(sexp).map_err(SexpError::from)?;
579        Ok(Some(slice))
580    }
581
582    #[inline]
583    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
584        if sexp.type_of() == SEXPTYPE::NILSXP {
585            return Ok(None);
586        }
587        let slice: &mut [T] =
588            unsafe { TryFromSexp::try_from_sexp_unchecked(sexp).map_err(SexpError::from)? };
589        Ok(Some(slice))
590    }
591}
592// endregion
593
594mod strings;
595
596// region: Result conversions (NULL -> Err(()))
597
598impl<T> TryFromSexp for Result<T, ()>
599where
600    T: TryFromSexp,
601    T::Error: Into<SexpError>,
602{
603    type Error = SexpError;
604
605    #[inline]
606    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
607        if sexp.type_of() == SEXPTYPE::NILSXP {
608            return Ok(Err(()));
609        }
610        let value = T::try_from_sexp(sexp).map_err(Into::into)?;
611        Ok(Ok(value))
612    }
613
614    #[inline]
615    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
616        if sexp.type_of() == SEXPTYPE::NILSXP {
617            return Ok(Err(()));
618        }
619        let value = unsafe { T::try_from_sexp_unchecked(sexp).map_err(Into::into)? };
620        Ok(Ok(value))
621    }
622}
623// endregion
624
625mod na_vectors;
626
627mod collections;
628
629// region: Fixed-size array conversions
630
631/// Blanket impl: Convert R vector to `[T; N]` where T: RNativeType.
632///
633/// Returns an error if the R vector length doesn't match N.
634/// Useful for SHA hashes ([u8; 32]), fixed-size patterns, etc.
635impl<T, const N: usize> TryFromSexp for [T; N]
636where
637    T: crate::ffi::RNativeType + Copy,
638{
639    type Error = SexpError;
640
641    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
642        let slice: &[T] = TryFromSexp::try_from_sexp(sexp)?;
643        if slice.len() != N {
644            return Err(SexpLengthError {
645                expected: N,
646                actual: slice.len(),
647            }
648            .into());
649        }
650
651        // T: Copy, length verified above. Use MaybeUninit + copy_from_slice.
652        let mut arr = std::mem::MaybeUninit::<[T; N]>::uninit();
653        unsafe {
654            // SAFETY: MaybeUninit<[T; N]> and [T; N] have the same layout.
655            // We write all N elements via copy_from_slice, so assume_init is safe.
656            let dst: &mut [T] = std::slice::from_raw_parts_mut(arr.as_mut_ptr().cast::<T>(), N);
657            dst.copy_from_slice(&slice[..N]);
658            Ok(arr.assume_init())
659        }
660    }
661
662    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
663        Self::try_from_sexp(sexp)
664    }
665}
666// endregion
667
668// region: VecDeque conversions
669
670use std::collections::VecDeque;
671
672/// Blanket impl: Convert R vector to `VecDeque<T>` where T: RNativeType.
673impl<T> TryFromSexp for VecDeque<T>
674where
675    T: crate::ffi::RNativeType + Copy,
676{
677    type Error = SexpTypeError;
678
679    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
680        let slice: &[T] = TryFromSexp::try_from_sexp(sexp)?;
681        Ok(VecDeque::from(slice.to_vec()))
682    }
683
684    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
685        let slice: &[T] = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
686        Ok(VecDeque::from(slice.to_vec()))
687    }
688}
689// endregion
690
691// region: BinaryHeap conversions
692
693use std::collections::BinaryHeap;
694
695/// Blanket impl: Convert R vector to `BinaryHeap<T>` where T: RNativeType + Ord.
696///
697/// Creates a binary heap from the R vector elements.
698impl<T> TryFromSexp for BinaryHeap<T>
699where
700    T: crate::ffi::RNativeType + Copy + Ord,
701{
702    type Error = SexpTypeError;
703
704    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
705        let slice: &[T] = TryFromSexp::try_from_sexp(sexp)?;
706        Ok(BinaryHeap::from(slice.to_vec()))
707    }
708
709    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
710        let slice: &[T] = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
711        Ok(BinaryHeap::from(slice.to_vec()))
712    }
713}
714// endregion
715
716mod cow_and_paths;
717
718// region: Option<Collection> conversions
719//
720// These convert NULL → None, and non-NULL to Some(collection).
721// This differs from Option<scalar> which converts NA → None.
722
723/// Convert R value to `Option<Vec<T>>`: NULL → None, otherwise Some(vec).
724impl<T> TryFromSexp for Option<Vec<T>>
725where
726    Vec<T>: TryFromSexp,
727    <Vec<T> as TryFromSexp>::Error: Into<SexpError>,
728{
729    type Error = SexpError;
730
731    #[inline]
732    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
733        if sexp.type_of() == SEXPTYPE::NILSXP {
734            Ok(None)
735        } else {
736            Vec::<T>::try_from_sexp(sexp).map(Some).map_err(Into::into)
737        }
738    }
739
740    #[inline]
741    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
742        if sexp.type_of() == SEXPTYPE::NILSXP {
743            Ok(None)
744        } else {
745            unsafe {
746                Vec::<T>::try_from_sexp_unchecked(sexp)
747                    .map(Some)
748                    .map_err(Into::into)
749            }
750        }
751    }
752}
753
754macro_rules! impl_option_map_try_from_sexp {
755    ($(#[$meta:meta])* $map_ty:ident) => {
756        $(#[$meta])*
757        impl<V: TryFromSexp> TryFromSexp for Option<$map_ty<String, V>>
758        where
759            V::Error: Into<SexpError>,
760        {
761            type Error = SexpError;
762
763            #[inline]
764            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
765                if sexp.type_of() == SEXPTYPE::NILSXP {
766                    Ok(None)
767                } else {
768                    $map_ty::<String, V>::try_from_sexp(sexp).map(Some)
769                }
770            }
771        }
772    };
773}
774
775impl_option_map_try_from_sexp!(
776    /// Convert R value to `Option<HashMap<String, V>>`: NULL -> None, otherwise Some(map).
777    HashMap
778);
779impl_option_map_try_from_sexp!(
780    /// Convert R value to `Option<BTreeMap<String, V>>`: NULL -> None, otherwise Some(map).
781    BTreeMap
782);
783
784macro_rules! impl_option_set_try_from_sexp {
785    ($(#[$meta:meta])* $set_ty:ident) => {
786        $(#[$meta])*
787        impl<T> TryFromSexp for Option<$set_ty<T>>
788        where
789            $set_ty<T>: TryFromSexp,
790            <$set_ty<T> as TryFromSexp>::Error: Into<SexpError>,
791        {
792            type Error = SexpError;
793
794            #[inline]
795            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
796                if sexp.type_of() == SEXPTYPE::NILSXP {
797                    Ok(None)
798                } else {
799                    $set_ty::<T>::try_from_sexp(sexp)
800                        .map(Some)
801                        .map_err(Into::into)
802                }
803            }
804        }
805    };
806}
807
808impl_option_set_try_from_sexp!(
809    /// Convert R value to `Option<HashSet<T>>`: NULL -> None, otherwise Some(set).
810    HashSet
811);
812impl_option_set_try_from_sexp!(
813    /// Convert R value to `Option<BTreeSet<T>>`: NULL -> None, otherwise Some(set).
814    BTreeSet
815);
816// endregion
817
818// region: Nested vector conversions (list of vectors)
819
820/// Convert R list (VECSXP) to `Vec<Vec<T>>`.
821///
822/// Each element of the R list must be convertible to `Vec<T>`.
823impl<T> TryFromSexp for Vec<Vec<T>>
824where
825    Vec<T>: TryFromSexp,
826    <Vec<T> as TryFromSexp>::Error: Into<SexpError>,
827{
828    type Error = SexpError;
829
830    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
831        let actual = sexp.type_of();
832        if actual != SEXPTYPE::VECSXP {
833            return Err(SexpTypeError {
834                expected: SEXPTYPE::VECSXP,
835                actual,
836            }
837            .into());
838        }
839
840        let len = sexp.len();
841        let mut result = Vec::with_capacity(len);
842
843        for i in 0..len {
844            let elem = sexp.vector_elt(i as crate::ffi::R_xlen_t);
845            let inner: Vec<T> = Vec::<T>::try_from_sexp(elem).map_err(Into::into)?;
846            result.push(inner);
847        }
848
849        Ok(result)
850    }
851
852    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
853        let actual = sexp.type_of();
854        if actual != SEXPTYPE::VECSXP {
855            return Err(SexpTypeError {
856                expected: SEXPTYPE::VECSXP,
857                actual,
858            }
859            .into());
860        }
861
862        let len = sexp.len();
863        let mut result = Vec::with_capacity(len);
864
865        for i in 0..len {
866            let elem = sexp.vector_elt(i as crate::ffi::R_xlen_t);
867            let inner: Vec<T> =
868                unsafe { Vec::<T>::try_from_sexp_unchecked(elem).map_err(Into::into)? };
869            result.push(inner);
870        }
871
872        Ok(result)
873    }
874}
875// endregion
876
877// region: Coerced wrapper - bridge between TryFromSexp and TryCoerce
878
879use crate::coerce::Coerced;
880
881/// Convert R value to `Coerced<T, R>` by reading `R` and coercing to `T`.
882///
883/// This enables reading non-native Rust types from R with coercion:
884///
885/// ```ignore
886/// // Read i64 from R integer (i32)
887/// let val: Coerced<i64, i32> = TryFromSexp::try_from_sexp(sexp)?;
888/// let i64_val: i64 = val.into_inner();
889///
890/// // Works with collections too:
891/// let vec: Vec<Coerced<i64, i32>> = ...;
892/// let set: HashSet<Coerced<NonZeroU32, i32>> = ...;
893/// ```
894impl<T, R> TryFromSexp for Coerced<T, R>
895where
896    R: TryFromSexp,
897    R: TryCoerce<T>,
898    <R as TryFromSexp>::Error: Into<SexpError>,
899    <R as TryCoerce<T>>::Error: std::fmt::Debug,
900{
901    type Error = SexpError;
902
903    #[inline]
904    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
905        let r_val: R = R::try_from_sexp(sexp).map_err(Into::into)?;
906        let value: T = r_val
907            .try_coerce()
908            .map_err(|e| SexpError::InvalidValue(format!("{e:?}")))?;
909        Ok(Coerced::new(value))
910    }
911
912    #[inline]
913    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
914        let r_val: R = unsafe { R::try_from_sexp_unchecked(sexp).map_err(Into::into)? };
915        let value: T = r_val
916            .try_coerce()
917            .map_err(|e| SexpError::InvalidValue(format!("{e:?}")))?;
918        Ok(Coerced::new(value))
919    }
920}
921// endregion
922
923// region: Direct Vec coercion conversions
924//
925// These provide direct `TryFromSexp for Vec<T>` where T is not an R native type
926// but can be coerced from one. This mirrors the `impl_into_r_via_coerce!` pattern
927// in into_r.rs for the reverse direction.
928
929/// Helper to coerce a slice element-wise into a Vec.
930#[inline]
931fn coerce_slice_to_vec<R, T>(slice: &[R]) -> Result<Vec<T>, SexpError>
932where
933    R: Copy + TryCoerce<T>,
934    <R as TryCoerce<T>>::Error: std::fmt::Debug,
935{
936    slice
937        .iter()
938        .copied()
939        .map(|v| {
940            v.try_coerce()
941                .map_err(|e| SexpError::InvalidValue(format!("{e:?}")))
942        })
943        .collect()
944}
945
946/// Convert numeric/logical/raw vectors to `Vec<T>` with element-wise coercion.
947#[inline]
948fn try_from_sexp_numeric_vec<T>(sexp: SEXP) -> Result<Vec<T>, SexpError>
949where
950    i32: TryCoerce<T>,
951    f64: TryCoerce<T>,
952    u8: TryCoerce<T>,
953    <i32 as TryCoerce<T>>::Error: std::fmt::Debug,
954    <f64 as TryCoerce<T>>::Error: std::fmt::Debug,
955    <u8 as TryCoerce<T>>::Error: std::fmt::Debug,
956{
957    let actual = sexp.type_of();
958    match actual {
959        SEXPTYPE::INTSXP => {
960            let slice: &[i32] = unsafe { sexp.as_slice() };
961            coerce_slice_to_vec(slice)
962        }
963        SEXPTYPE::REALSXP => {
964            let slice: &[f64] = unsafe { sexp.as_slice() };
965            coerce_slice_to_vec(slice)
966        }
967        SEXPTYPE::RAWSXP => {
968            let slice: &[u8] = unsafe { sexp.as_slice() };
969            coerce_slice_to_vec(slice)
970        }
971        SEXPTYPE::LGLSXP => {
972            let slice: &[RLogical] = unsafe { sexp.as_slice() };
973            slice.iter().map(|v| coerce_value(v.to_i32())).collect()
974        }
975        _ => Err(SexpError::InvalidValue(format!(
976            "expected integer, numeric, logical, or raw; got {:?}",
977            actual
978        ))),
979    }
980}
981
982/// Implement `TryFromSexp for Vec<$target>` by coercing from integer/real/logical/raw.
983macro_rules! impl_vec_try_from_sexp_numeric {
984    ($target:ty) => {
985        impl TryFromSexp for Vec<$target> {
986            type Error = SexpError;
987
988            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
989                try_from_sexp_numeric_vec(sexp)
990            }
991
992            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
993                try_from_sexp_numeric_vec(sexp)
994            }
995        }
996    };
997}
998
999impl_vec_try_from_sexp_numeric!(i8);
1000impl_vec_try_from_sexp_numeric!(i16);
1001impl_vec_try_from_sexp_numeric!(i64);
1002impl_vec_try_from_sexp_numeric!(isize);
1003impl_vec_try_from_sexp_numeric!(u16);
1004impl_vec_try_from_sexp_numeric!(u32);
1005impl_vec_try_from_sexp_numeric!(u64);
1006impl_vec_try_from_sexp_numeric!(usize);
1007impl_vec_try_from_sexp_numeric!(f32);
1008
1009/// Convert R logical vector (LGLSXP) to `Vec<bool>` (errors on NA).
1010impl TryFromSexp for Vec<bool> {
1011    type Error = SexpError;
1012
1013    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1014        let actual = sexp.type_of();
1015        if actual != SEXPTYPE::LGLSXP {
1016            return Err(SexpTypeError {
1017                expected: SEXPTYPE::LGLSXP,
1018                actual,
1019            }
1020            .into());
1021        }
1022        let slice: &[RLogical] = unsafe { sexp.as_slice() };
1023        coerce_slice_to_vec(slice)
1024    }
1025
1026    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1027        Self::try_from_sexp(sexp)
1028    }
1029}
1030
1031impl TryFromSexp for Box<[bool]> {
1032    type Error = SexpError;
1033
1034    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1035        let vec: Vec<bool> = TryFromSexp::try_from_sexp(sexp)?;
1036        Ok(vec.into_boxed_slice())
1037    }
1038}
1039// endregion
1040
1041// region: Direct HashSet / BTreeSet coercion conversions
1042
1043/// Convert numeric/logical/raw vectors to a set type with element-wise coercion.
1044#[inline]
1045fn try_from_sexp_numeric_set<T, S>(sexp: SEXP) -> Result<S, SexpError>
1046where
1047    S: std::iter::FromIterator<T>,
1048    i32: TryCoerce<T>,
1049    f64: TryCoerce<T>,
1050    u8: TryCoerce<T>,
1051    <i32 as TryCoerce<T>>::Error: std::fmt::Debug,
1052    <f64 as TryCoerce<T>>::Error: std::fmt::Debug,
1053    <u8 as TryCoerce<T>>::Error: std::fmt::Debug,
1054{
1055    let vec = try_from_sexp_numeric_vec(sexp)?;
1056    Ok(vec.into_iter().collect())
1057}
1058
1059macro_rules! impl_set_try_from_sexp_numeric {
1060    ($set_ty:ident, $target:ty) => {
1061        impl TryFromSexp for $set_ty<$target> {
1062            type Error = SexpError;
1063
1064            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1065                try_from_sexp_numeric_set(sexp)
1066            }
1067
1068            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1069                try_from_sexp_numeric_set(sexp)
1070            }
1071        }
1072    };
1073}
1074
1075impl_set_try_from_sexp_numeric!(HashSet, i8);
1076impl_set_try_from_sexp_numeric!(HashSet, i16);
1077impl_set_try_from_sexp_numeric!(HashSet, i64);
1078impl_set_try_from_sexp_numeric!(HashSet, isize);
1079impl_set_try_from_sexp_numeric!(HashSet, u16);
1080impl_set_try_from_sexp_numeric!(HashSet, u32);
1081impl_set_try_from_sexp_numeric!(HashSet, u64);
1082impl_set_try_from_sexp_numeric!(HashSet, usize);
1083
1084impl_set_try_from_sexp_numeric!(BTreeSet, i8);
1085impl_set_try_from_sexp_numeric!(BTreeSet, i16);
1086impl_set_try_from_sexp_numeric!(BTreeSet, i64);
1087impl_set_try_from_sexp_numeric!(BTreeSet, isize);
1088impl_set_try_from_sexp_numeric!(BTreeSet, u16);
1089impl_set_try_from_sexp_numeric!(BTreeSet, u32);
1090impl_set_try_from_sexp_numeric!(BTreeSet, u64);
1091impl_set_try_from_sexp_numeric!(BTreeSet, usize);
1092
1093macro_rules! impl_set_try_from_sexp_bool {
1094    ($set_ty:ident) => {
1095        impl TryFromSexp for $set_ty<bool> {
1096            type Error = SexpError;
1097
1098            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1099                let vec: Vec<bool> = TryFromSexp::try_from_sexp(sexp)?;
1100                Ok(vec.into_iter().collect())
1101            }
1102
1103            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1104                Self::try_from_sexp(sexp)
1105            }
1106        }
1107    };
1108}
1109
1110impl_set_try_from_sexp_bool!(HashSet);
1111impl_set_try_from_sexp_bool!(BTreeSet);
1112// endregion
1113
1114// region: ExternalPtr conversions
1115
1116use crate::externalptr::{ExternalPtr, TypeMismatchError, TypedExternal};
1117
1118/// Convert R EXTPTRSXP to `ExternalPtr<T>`.
1119///
1120/// This enables using `ExternalPtr<T>` as parameter types in `#[miniextendr]` functions.
1121///
1122/// # Example
1123///
1124/// ```ignore
1125/// #[derive(ExternalPtr)]
1126/// struct MyData { value: i32 }
1127///
1128/// #[miniextendr]
1129/// fn process(data: ExternalPtr<MyData>) -> i32 {
1130///     data.value
1131/// }
1132/// ```
1133impl<T: TypedExternal + Send> TryFromSexp for ExternalPtr<T> {
1134    type Error = SexpError;
1135
1136    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1137        let actual = sexp.type_of();
1138        if actual != SEXPTYPE::EXTPTRSXP {
1139            return Err(SexpTypeError {
1140                expected: SEXPTYPE::EXTPTRSXP,
1141                actual,
1142            }
1143            .into());
1144        }
1145
1146        // Use ExternalPtr's type-checked constructor
1147        unsafe { ExternalPtr::wrap_sexp_with_error(sexp) }.map_err(|e| match e {
1148            TypeMismatchError::NullPointer => {
1149                SexpError::InvalidValue("external pointer is null".to_string())
1150            }
1151            TypeMismatchError::InvalidTypeId => {
1152                SexpError::InvalidValue("external pointer has no valid type id".to_string())
1153            }
1154            TypeMismatchError::Mismatch { expected, found } => SexpError::InvalidValue(format!(
1155                "type mismatch: expected `{}`, found `{}`",
1156                expected, found
1157            )),
1158        })
1159    }
1160
1161    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1162        let actual = sexp.type_of();
1163        if actual != SEXPTYPE::EXTPTRSXP {
1164            return Err(SexpTypeError {
1165                expected: SEXPTYPE::EXTPTRSXP,
1166                actual,
1167            }
1168            .into());
1169        }
1170
1171        // Use ExternalPtr's type-checked constructor (unchecked variant)
1172        unsafe { ExternalPtr::wrap_sexp_unchecked(sexp) }.ok_or_else(|| {
1173            SexpError::InvalidValue(
1174                "failed to convert external pointer: type mismatch or null pointer".to_string(),
1175            )
1176        })
1177    }
1178}
1179
1180impl<T: TypedExternal + Send> TryFromSexp for Option<ExternalPtr<T>> {
1181    type Error = SexpError;
1182
1183    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1184        if sexp.type_of() == SEXPTYPE::NILSXP {
1185            return Ok(None);
1186        }
1187        let ptr: ExternalPtr<T> = TryFromSexp::try_from_sexp(sexp)?;
1188        Ok(Some(ptr))
1189    }
1190
1191    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1192        if sexp.type_of() == SEXPTYPE::NILSXP {
1193            return Ok(None);
1194        }
1195        let ptr: ExternalPtr<T> = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
1196        Ok(Some(ptr))
1197    }
1198}
1199// endregion
1200
1201// region: Helper macros for feature-gated modules
1202
1203/// Implement `TryFromSexp for Option<T>` where T already implements TryFromSexp.
1204///
1205/// NULL → None, otherwise delegates to T::try_from_sexp and wraps in Some.
1206#[macro_export]
1207macro_rules! impl_option_try_from_sexp {
1208    ($t:ty) => {
1209        impl $crate::from_r::TryFromSexp for Option<$t> {
1210            type Error = $crate::from_r::SexpError;
1211
1212            fn try_from_sexp(sexp: $crate::ffi::SEXP) -> Result<Self, Self::Error> {
1213                use $crate::ffi::{SEXPTYPE, SexpExt};
1214                if sexp.type_of() == SEXPTYPE::NILSXP {
1215                    return Ok(None);
1216                }
1217                <$t as $crate::from_r::TryFromSexp>::try_from_sexp(sexp).map(Some)
1218            }
1219
1220            unsafe fn try_from_sexp_unchecked(
1221                sexp: $crate::ffi::SEXP,
1222            ) -> Result<Self, Self::Error> {
1223                use $crate::ffi::{SEXPTYPE, SexpExt};
1224                if sexp.type_of() == SEXPTYPE::NILSXP {
1225                    return Ok(None);
1226                }
1227                unsafe {
1228                    <$t as $crate::from_r::TryFromSexp>::try_from_sexp_unchecked(sexp).map(Some)
1229                }
1230            }
1231        }
1232    };
1233}
1234
1235/// Implement `TryFromSexp for Vec<T>` from R list (VECSXP).
1236///
1237/// Each element is converted via T::try_from_sexp.
1238#[macro_export]
1239macro_rules! impl_vec_try_from_sexp_list {
1240    ($t:ty) => {
1241        impl $crate::from_r::TryFromSexp for Vec<$t> {
1242            type Error = $crate::from_r::SexpError;
1243
1244            fn try_from_sexp(sexp: $crate::ffi::SEXP) -> Result<Self, Self::Error> {
1245                use $crate::ffi::{SEXPTYPE, SexpExt};
1246                use $crate::from_r::SexpTypeError;
1247
1248                let actual = sexp.type_of();
1249                if actual != SEXPTYPE::VECSXP {
1250                    return Err(SexpTypeError {
1251                        expected: SEXPTYPE::VECSXP,
1252                        actual,
1253                    }
1254                    .into());
1255                }
1256
1257                let len = sexp.len();
1258                let mut result = Vec::with_capacity(len);
1259                for i in 0..len {
1260                    let elem = sexp.vector_elt(i as $crate::ffi::R_xlen_t);
1261                    result.push(<$t as $crate::from_r::TryFromSexp>::try_from_sexp(elem)?);
1262                }
1263                Ok(result)
1264            }
1265
1266            unsafe fn try_from_sexp_unchecked(
1267                sexp: $crate::ffi::SEXP,
1268            ) -> Result<Self, Self::Error> {
1269                use $crate::ffi::{SEXPTYPE, SexpExt};
1270                use $crate::from_r::SexpTypeError;
1271
1272                let actual = sexp.type_of();
1273                if actual != SEXPTYPE::VECSXP {
1274                    return Err(SexpTypeError {
1275                        expected: SEXPTYPE::VECSXP,
1276                        actual,
1277                    }
1278                    .into());
1279                }
1280
1281                let len = unsafe { sexp.len_unchecked() };
1282                let mut result = Vec::with_capacity(len);
1283                for i in 0..len {
1284                    let elem = unsafe { sexp.vector_elt_unchecked(i as $crate::ffi::R_xlen_t) };
1285                    result.push(unsafe {
1286                        <$t as $crate::from_r::TryFromSexp>::try_from_sexp_unchecked(elem)?
1287                    });
1288                }
1289                Ok(result)
1290            }
1291        }
1292    };
1293}
1294
1295/// Implement `TryFromSexp for Vec<Option<T>>` from R list (VECSXP).
1296///
1297/// NULL elements become None, others are converted via T::try_from_sexp.
1298#[macro_export]
1299macro_rules! impl_vec_option_try_from_sexp_list {
1300    ($t:ty) => {
1301        impl $crate::from_r::TryFromSexp for Vec<Option<$t>> {
1302            type Error = $crate::from_r::SexpError;
1303
1304            fn try_from_sexp(sexp: $crate::ffi::SEXP) -> Result<Self, Self::Error> {
1305                use $crate::ffi::{SEXPTYPE, SexpExt};
1306                use $crate::from_r::SexpTypeError;
1307
1308                let actual = sexp.type_of();
1309                if actual != SEXPTYPE::VECSXP {
1310                    return Err(SexpTypeError {
1311                        expected: SEXPTYPE::VECSXP,
1312                        actual,
1313                    }
1314                    .into());
1315                }
1316
1317                let len = sexp.len();
1318                let mut result = Vec::with_capacity(len);
1319                for i in 0..len {
1320                    let elem = sexp.vector_elt(i as $crate::ffi::R_xlen_t);
1321                    if elem == $crate::ffi::SEXP::nil() {
1322                        result.push(None);
1323                    } else {
1324                        result.push(Some(<$t as $crate::from_r::TryFromSexp>::try_from_sexp(
1325                            elem,
1326                        )?));
1327                    }
1328                }
1329                Ok(result)
1330            }
1331
1332            unsafe fn try_from_sexp_unchecked(
1333                sexp: $crate::ffi::SEXP,
1334            ) -> Result<Self, Self::Error> {
1335                use $crate::ffi::{SEXPTYPE, SexpExt};
1336                use $crate::from_r::SexpTypeError;
1337
1338                let actual = sexp.type_of();
1339                if actual != SEXPTYPE::VECSXP {
1340                    return Err(SexpTypeError {
1341                        expected: SEXPTYPE::VECSXP,
1342                        actual,
1343                    }
1344                    .into());
1345                }
1346
1347                let len = unsafe { sexp.len_unchecked() };
1348                let mut result = Vec::with_capacity(len);
1349                for i in 0..len {
1350                    let elem = unsafe { sexp.vector_elt_unchecked(i as $crate::ffi::R_xlen_t) };
1351                    if elem == $crate::ffi::SEXP::nil() {
1352                        result.push(None);
1353                    } else {
1354                        result.push(Some(unsafe {
1355                            <$t as $crate::from_r::TryFromSexp>::try_from_sexp_unchecked(elem)?
1356                        }));
1357                    }
1358                }
1359                Ok(result)
1360            }
1361        }
1362    };
1363}
1364// endregion