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` | `STRING_ELT()` + `R_CHAR()` (UTF-8 locale asserted at init) |
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//! # Choosing the right inbound conversion
28//!
29//! [`TryFromSexp`] is the strict inbound path: it returns `Result<T, SexpError>`
30//! and rejects mismatched [`SEXPTYPE`]s outright (no silent coercion). When you
31//! need to *accept* arguments coming from multiple R native types, reach for
32//! the [`crate::coerce::Coerce`] / [`crate::coerce::TryCoerce`] traits instead
33//! — those are the looser inbound path and the entry point for the multi-source
34//! scalars handled in [`coerced_scalars`].
35//!
36//! The strict-vs-lax pairing for *outbound* conversion lives on
37//! [`crate::into_r::IntoR`] (lax, default) vs [`crate::strict`] (`#[miniextendr(strict)]`).
38//! There is intentionally no `TryFromSexpStrict` trait — inbound is already
39//! strict-by-default because it returns `Result`.
40//!
41//! # Thread Safety
42//!
43//! The trait provides two methods:
44//! - [`TryFromSexp::try_from_sexp`] - checked version with debug thread assertions
45//! - [`TryFromSexp::try_from_sexp_unchecked`] - unchecked version for performance-critical paths
46//!
47//! Use `try_from_sexp_unchecked` when you're certain you're on the main thread:
48//! - Inside ALTREP callbacks
49//! - Inside standalone `#[miniextendr]` functions (they run on the main thread)
50//! - Inside `extern "C-unwind"` functions called directly by R
51
52use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
53
54use crate::altrep_traits::NA_REAL;
55use crate::coerce::TryCoerce;
56use crate::{IsNA, RLogical, SEXP, SEXPTYPE, SexpExt};
57
58/// Whether R's `anyNA(sexp)` is `TRUE`: the check `no_na`'s R guard makes,
59/// done in Rust on an argument that the guard does not cover (see
60/// [`TryFromSexp::__mx_input_has_na`]).
61///
62/// It follows R's rules (`anyNA` / `is.na` in `src/main/coerce.c`). A double
63/// or complex value counts when it is `NaN` (`ISNAN`, so `NaN` counts as well
64/// as `NA`). A logical or integer counts when it is `NA_INTEGER`, and a string
65/// when it is `NA_STRING`. Raw vectors and `NULL` never count. A list counts
66/// when one of its elements is a length-1 atomic `NA` (`anyNA()` is not
67/// recursive by default), and a data frame when one of its columns counts
68/// (`anyNA.data.frame`). Any other type (an external pointer, an environment,
69/// ...) holds no `NA`. A classed value is read by its type. The one gap is a
70/// class with its own `is.na()` method, such as `POSIXlt`.
71///
72/// An ALTREP vector is read element by element (`*_ELT`), as R does: a data
73/// pointer would expand a compact sequence, and some classes have none.
74pub(crate) fn any_na(sexp: SEXP) -> bool {
75    if !sexp.is_vector() {
76        return false;
77    }
78    let n = sexp.xlength();
79    if sexp.is_data_frame() {
80        return (0..n).any(|i| any_na(sexp.vector_elt(i)));
81    }
82    let altrep = sexp.is_altrep();
83    // SAFETY: `sexp` is a live wrapper argument (or an element of one) for
84    // the whole check, and each slice is read at the SEXP's own type.
85    match sexp.type_of() {
86        SEXPTYPE::LGLSXP if altrep => {
87            (0..n).any(|i| RLogical::from_i32(sexp.logical_elt(i)).is_na())
88        }
89        SEXPTYPE::INTSXP if altrep => (0..n).any(|i| sexp.integer_elt(i).is_na()),
90        SEXPTYPE::REALSXP if altrep => (0..n).any(|i| sexp.real_elt(i).is_nan()),
91        SEXPTYPE::CPLXSXP if altrep => (0..n).any(|i| {
92            let v = sexp.complex_elt(i);
93            v.r.is_nan() || v.i.is_nan()
94        }),
95        SEXPTYPE::LGLSXP => unsafe { sexp.as_slice::<RLogical>() }
96            .iter()
97            .any(|v| v.is_na()),
98        SEXPTYPE::INTSXP => unsafe { sexp.as_slice::<i32>() }.iter().any(IsNA::is_na),
99        SEXPTYPE::REALSXP => unsafe { sexp.as_slice::<f64>() }.iter().any(|v| v.is_nan()),
100        SEXPTYPE::CPLXSXP => unsafe { sexp.as_slice::<crate::Rcomplex>() }
101            .iter()
102            .any(|v| v.r.is_nan() || v.i.is_nan()),
103        SEXPTYPE::STRSXP => (0..n).any(|i| sexp.string_elt(i).is_na_string()),
104        SEXPTYPE::VECSXP => (0..n).any(|i| {
105            let elt = sexp.vector_elt(i);
106            elt.is_vector_atomic() && elt.len() == 1 && any_na(elt)
107        }),
108        _ => false,
109    }
110}
111
112// region: CHARSXP to string conversion
113
114/// Convert CHARSXP to `&str` — zero-copy from R's string data.
115///
116/// Uses `R_CHAR` + `LENGTH` (O(1), no strlen). UTF-8 validity is guaranteed
117/// by `miniextendr_assert_utf8_locale()` at package init, so no per-string
118/// validation is needed.
119///
120/// # Safety
121///
122/// - `charsxp` must be a valid CHARSXP (not NA_STRING, not null).
123/// - The returned `&str` is only valid as long as R doesn't GC the CHARSXP.
124#[inline]
125pub(crate) unsafe fn charsxp_to_str(charsxp: SEXP) -> &'static str {
126    unsafe { charsxp_to_str_impl(charsxp.r_char(), charsxp) }
127}
128
129/// Unchecked version of [`charsxp_to_str`] (skips R thread checks on `R_CHAR`).
130#[inline]
131pub(crate) unsafe fn charsxp_to_str_unchecked(charsxp: SEXP) -> &'static str {
132    unsafe { charsxp_to_str_impl(charsxp.r_char_unchecked(), charsxp) }
133}
134
135/// Shared implementation: given a data pointer and CHARSXP, produce `&str`.
136///
137/// UTF-8 locale is asserted at init — `from_utf8_unchecked` is safe.
138#[inline]
139unsafe fn charsxp_to_str_impl(ptr: *const std::os::raw::c_char, charsxp: SEXP) -> &'static str {
140    unsafe {
141        let len: usize = charsxp.len();
142        let bytes = r_slice(ptr.cast::<u8>(), len);
143        // SAFETY: miniextendr_assert_utf8_locale() at init guarantees all
144        // CHARSXPs in this session are valid UTF-8 or ASCII.
145        if true {
    if !std::str::from_utf8(bytes).is_ok() {
        {
            ::core::panicking::panic_fmt(format_args!("CHARSXP contains non-UTF-8 bytes (locale assertion may have been skipped)"));
        }
    };
};debug_assert!(
146            std::str::from_utf8(bytes).is_ok(),
147            "CHARSXP contains non-UTF-8 bytes (locale assertion may have been skipped)"
148        );
149        std::str::from_utf8_unchecked(bytes)
150    }
151}
152
153/// `charsxp_to_cow` is now just an alias — all CHARSXPs are UTF-8 (asserted
154/// at init), so there's no non-UTF-8 fallback path. Returns `Cow::Borrowed`.
155#[inline]
156pub(crate) unsafe fn charsxp_to_cow(charsxp: SEXP) -> std::borrow::Cow<'static, str> {
157    std::borrow::Cow::Borrowed(unsafe { charsxp_to_str(charsxp) })
158}
159
160/// Convert CHARSXP to an owned, lossy `String`.
161///
162/// NA/null-defensive: returns `None` for `NA_character_`, `R_NilValue`, or a
163/// null SEXP. Non-UTF-8 bytes are replaced (`CStr::to_string_lossy`) rather
164/// than rejected. Unlike [`charsxp_to_str`] (the UTF-8-asserted hot path for
165/// package-internal CHARSXPs), this is for defensive reads of *arbitrary* R
166/// objects — S4 class attributes, `geterrmessage()` output, vctrs field
167/// names, `tzone` attributes — where the CHARSXP's origin and encoding
168/// aren't guaranteed.
169///
170/// # Safety
171///
172/// `charsxp` must be a valid SEXP. It may be `R_NilValue` or a null SEXP
173/// (both map to `None`); if it is neither of those and not `NA_character_`,
174/// it must actually be a CHARSXP.
175#[inline]
176pub(crate) unsafe fn charsxp_to_string_lossy(charsxp: SEXP) -> Option<String> {
177    if charsxp.is_null_or_nil() || charsxp.is_na_string() {
178        return None;
179    }
180    let ptr = charsxp.r_char();
181    if ptr.is_null() {
182        return None;
183    }
184    Some(
185        unsafe { std::ffi::CStr::from_ptr(ptr) }
186            .to_string_lossy()
187            .into_owned(),
188    )
189}
190
191/// Create a slice from an R data pointer, handling the zero-length case.
192///
193/// R returns a sentinel pointer (`0x1`) instead of null for empty vectors
194/// (e.g., `LOGICAL(integer(0))` → `0x1`). Rust 1.93+ validates pointer
195/// alignment in `slice::from_raw_parts` even for `len == 0`, so passing
196/// R's sentinel directly causes a precondition-check abort.
197///
198/// This helper returns an empty slice for `len == 0` without touching the pointer.
199///
200/// # Safety
201///
202/// If `len > 0`, `ptr` must satisfy the requirements of [`std::slice::from_raw_parts`].
203#[inline(always)]
204pub(crate) unsafe fn r_slice<'a, T>(ptr: *const T, len: usize) -> &'a [T] {
205    if len == 0 {
206        &[]
207    } else {
208        unsafe { std::slice::from_raw_parts(ptr, len) }
209    }
210}
211
212/// Mutable version of [`r_slice`] for `from_raw_parts_mut`.
213///
214/// # Safety
215///
216/// If `len > 0`, `ptr` must satisfy the requirements of [`std::slice::from_raw_parts_mut`].
217#[inline(always)]
218pub(crate) unsafe fn r_slice_mut<'a, T>(ptr: *mut T, len: usize) -> &'a mut [T] {
219    if len == 0 {
220        &mut []
221    } else {
222        unsafe { std::slice::from_raw_parts_mut(ptr, len) }
223    }
224}
225
226#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SexpTypeError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "SexpTypeError",
            "expected", &self.expected, "actual", &&self.actual)
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for SexpTypeError { }
#[automatically_derived]
impl ::core::clone::Clone for SexpTypeError {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<SEXPTYPE>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for SexpTypeError { }Copy)]
227/// Error describing an unexpected R `SEXPTYPE`.
228pub struct SexpTypeError {
229    /// Expected R type.
230    pub expected: SEXPTYPE,
231    /// Actual R type encountered.
232    pub actual: SEXPTYPE,
233}
234
235impl std::fmt::Display for SexpTypeError {
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237        f.write_fmt(format_args!("type mismatch: expected {0:?}, got {1:?}",
        self.expected, self.actual))write!(
238            f,
239            "type mismatch: expected {:?}, got {:?}",
240            self.expected, self.actual
241        )
242    }
243}
244
245impl std::error::Error for SexpTypeError {}
246
247#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SexpClassError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "SexpClassError", "expected", &self.expected, "actual",
            &&self.actual)
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for SexpClassError { }
#[automatically_derived]
impl ::core::clone::Clone for SexpClassError {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<&'static str>;
        let _: ::core::clone::AssertParamIsClone<SEXPTYPE>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for SexpClassError { }Copy)]
248/// Error describing a value that is not the kind of R object a conversion
249/// reads, where that kind is a class rather than a `SEXPTYPE` (a data frame
250/// is a list with a class).
251pub struct SexpClassError {
252    /// What the value should have been, in R terms, with its article:
253    /// `"a data frame"`.
254    pub expected: &'static str,
255    /// Actual R type encountered.
256    pub actual: SEXPTYPE,
257}
258
259impl std::fmt::Display for SexpClassError {
260    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261        f.write_fmt(format_args!("class mismatch: expected {0}, got {1:?}",
        self.expected, self.actual))write!(
262            f,
263            "class mismatch: expected {}, got {:?}",
264            self.expected, self.actual
265        )
266    }
267}
268
269impl std::error::Error for SexpClassError {}
270
271#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SexpLengthError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "SexpLengthError", "expected", &self.expected, "actual",
            &&self.actual)
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for SexpLengthError { }
#[automatically_derived]
impl ::core::clone::Clone for SexpLengthError {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<usize>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for SexpLengthError { }Copy)]
272/// Error describing an unexpected R object length.
273pub struct SexpLengthError {
274    /// Required length.
275    pub expected: usize,
276    /// Actual length encountered.
277    pub actual: usize,
278}
279
280impl std::fmt::Display for SexpLengthError {
281    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
282        f.write_fmt(format_args!("length mismatch: expected {0}, got {1}",
        self.expected, self.actual))write!(
283            f,
284            "length mismatch: expected {}, got {}",
285            self.expected, self.actual
286        )
287    }
288}
289
290impl std::error::Error for SexpLengthError {}
291
292#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SexpNaError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field1_finish(f, "SexpNaError",
            "sexp_type", &&self.sexp_type)
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for SexpNaError { }
#[automatically_derived]
impl ::core::clone::Clone for SexpNaError {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<SEXPTYPE>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for SexpNaError { }Copy)]
293/// Error for NA values in conversions that require non-missing values.
294pub struct SexpNaError {
295    /// R type where an NA was found.
296    pub sexp_type: SEXPTYPE,
297}
298
299impl std::fmt::Display for SexpNaError {
300    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
301        f.write_fmt(format_args!("unexpected NA value in {0:?}", self.sexp_type))write!(f, "unexpected NA value in {:?}", self.sexp_type)
302    }
303}
304
305impl std::error::Error for SexpNaError {}
306
307#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SexpError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Self::Type(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Type",
                    &__self_0),
            Self::Class(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Class",
                    &__self_0),
            Self::Length(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Length",
                    &__self_0),
            Self::Na(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Na",
                    &__self_0),
            Self::InvalidValue(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "InvalidValue", &__self_0),
            Self::MissingField(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "MissingField", &__self_0),
            Self::DuplicateName(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "DuplicateName", &__self_0),
            Self::MatchArg(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "MatchArg", &__self_0),
            Self::Condition(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Condition", &__self_0),
        }
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for SexpError {
    #[inline]
    fn clone(&self) -> Self {
        match self {
            Self::Type(__self_0) =>
                SexpError::Type(::core::clone::Clone::clone(__self_0)),
            Self::Class(__self_0) =>
                SexpError::Class(::core::clone::Clone::clone(__self_0)),
            Self::Length(__self_0) =>
                SexpError::Length(::core::clone::Clone::clone(__self_0)),
            Self::Na(__self_0) =>
                SexpError::Na(::core::clone::Clone::clone(__self_0)),
            Self::InvalidValue(__self_0) =>
                SexpError::InvalidValue(::core::clone::Clone::clone(__self_0)),
            Self::MissingField(__self_0) =>
                SexpError::MissingField(::core::clone::Clone::clone(__self_0)),
            Self::DuplicateName(__self_0) =>
                SexpError::DuplicateName(::core::clone::Clone::clone(__self_0)),
            Self::MatchArg(__self_0) =>
                SexpError::MatchArg(::core::clone::Clone::clone(__self_0)),
            Self::Condition(__self_0) =>
                SexpError::Condition(::core::clone::Clone::clone(__self_0)),
        }
    }
}Clone)]
308/// Unified conversion error when decoding an R `SEXP`.
309pub enum SexpError {
310    /// `SEXPTYPE` did not match the expected one.
311    Type(SexpTypeError),
312    /// The value is not the kind of R object this conversion reads, where
313    /// that kind is a class (a data frame is a list with a class), not a
314    /// `SEXPTYPE`.
315    Class(SexpClassError),
316    /// Length did not match the expected one.
317    Length(SexpLengthError),
318    /// Missing value encountered where disallowed.
319    Na(SexpNaError),
320    /// Value is syntactically valid but semantically invalid (e.g. parse error).
321    InvalidValue(String),
322    /// A required field was missing from a named list.
323    MissingField(String),
324    /// A named list has duplicate non-empty names.
325    DuplicateName(String),
326    /// A `match_arg` choice did not match (from `#[derive(MatchArg)]`'s
327    /// `TryFromSexp`), kept whole so the argument error names the choices.
328    MatchArg(crate::match_arg::MatchArgError),
329    /// A value a check refused with its own message, classes and fields,
330    /// typically a [`TryFromSexpElement::check_sexp`](crate::TryFromSexpElement::check_sexp)
331    /// (`#[try_from_sexp(validate = ...)]`). Any
332    /// [`RConditionData`](crate::condition::RConditionData) type, an
333    /// [`RError`](crate::condition::RError) or a `#[derive(RCondition)]`
334    /// type, converts into it with `?`.
335    ///
336    /// As an argument error it keeps the parameter context: the message is
337    /// `'<p>' must be <expected>: <the RError's message>`, the classes come
338    /// before the crate's `conversion_error_class`, the fields are spliced in
339    /// next to `e$param` and `e$rust_type`, and the call is the wrapper's
340    /// ([`RError::without_call`](crate::condition::RError::without_call) has
341    /// no effect here, as for any argument error). Inside an `Either` it is
342    /// reported, classes and fields included, whenever the other arm refused
343    /// the kind of value.
344    Condition(crate::condition::RError),
345    /// Failed to convert to `Either<L, R>` - both branches failed.
346    ///
347    /// Contains the errors from attempting both conversions, so the argument
348    /// error can word them for R (see [`SexpError::r_reason`]).
349    #[cfg(feature = "either")]
350    EitherConversion {
351        /// Error from attempting to convert to the Left type
352        left_error: Box<SexpError>,
353        /// Error from attempting to convert to the Right type
354        right_error: Box<SexpError>,
355    },
356}
357
358impl SexpTypeError {
359    /// The reason of this type error in R terms (#1591): `got character`, or
360    /// `expected integer, got character` when `expected_known` is `false`
361    /// (nothing before the reason says what the value should be). The type
362    /// names are [`sexptype_name`](crate::typed_list::sexptype_name)'s
363    /// (`numeric` for a double, `list`, `NULL`, ...), not SEXPTYPE names.
364    ///
365    /// A pairlist where a list was expected says how to fix it: `got
366    /// pairlist, convert it with as.list()`. R's `is.list()` is `TRUE` for a
367    /// pairlist, but a list conversion refuses one (#1866).
368    pub(crate) fn r_reason(&self, expected_known: bool) -> String {
369        let got = crate::typed_list::sexptype_name(self.actual);
370        let hint = if self.expected == SEXPTYPE::VECSXP && self.actual == SEXPTYPE::LISTSXP {
371            ", convert it with as.list()"
372        } else {
373            ""
374        };
375        if expected_known {
376            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("got {0}{1}", got, hint))
    })format!("got {got}{hint}")
377        } else {
378            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("expected {0}, got {1}{2}",
                crate::typed_list::sexptype_name(self.expected), got, hint))
    })format!(
379                "expected {}, got {got}{hint}",
380                crate::typed_list::sexptype_name(self.expected)
381            )
382        }
383    }
384}
385
386impl SexpClassError {
387    /// The reason of this class error in R terms: `got list`, or
388    /// `expected a data frame, got list` when `expected_known` is `false`.
389    pub(crate) fn r_reason(&self, expected_known: bool) -> String {
390        let got = crate::typed_list::sexptype_name(self.actual);
391        if expected_known {
392            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("got {0}", got))
    })format!("got {got}")
393        } else {
394            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("expected {0}, got {1}",
                self.expected, got))
    })format!("expected {}, got {got}", self.expected)
395        }
396    }
397}
398
399impl SexpLengthError {
400    /// The reason of this length error in R terms: `got length 2`, or
401    /// `expected length 1, got length 2` when `expected_known` is `false`.
402    pub(crate) fn r_reason(&self, expected_known: bool) -> String {
403        if expected_known {
404            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("got length {0}", self.actual))
    })format!("got length {}", self.actual)
405        } else {
406            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("expected length {0}, got length {1}",
                self.expected, self.actual))
    })format!(
407                "expected length {}, got length {}",
408                self.expected, self.actual
409            )
410        }
411    }
412}
413
414/// What one arm of an `Either` expected, when the value was not of that arm's
415/// kind at all (see [`SexpError::kind_mismatch`]).
416#[cfg(feature = "either")]
417enum KindExpected {
418    /// A kind in R terms: `numeric`, `a data frame`, or the joined
419    /// expectation of a nested `Either`.
420    Noun(String),
421    /// A `match_arg` choice, which reads only a string or factor: the
422    /// type's choices and aliases, whose listed ones the expectation names
423    /// after the choices.
424    Choices(
425        &'static [&'static str],
426        &'static [crate::match_arg::MatchArgAlias],
427    ),
428}
429
430#[cfg(feature = "either")]
431impl KindExpected {
432    /// The expectation of an `Either` over this arm (left) and `right`:
433    /// `numeric or a data frame`, `one of "a", "b", or numeric`,
434    /// `numeric or one of "a", "b"`, and one `one of` over two choice lists
435    /// (and their aliases).
436    fn join(&self, right: &KindExpected) -> String {
437        use crate::match_arg::choice_expectation;
438        match (self, right) {
439            (KindExpected::Noun(l), KindExpected::Noun(r)) => format!("{l} or {r}"),
440            (KindExpected::Choices(l, aliases), KindExpected::Noun(r)) => {
441                choice_expectation(l, aliases, false, &format!(", or {r}"))
442            }
443            (KindExpected::Noun(l), KindExpected::Choices(r, aliases)) => {
444                format!("{l} or {}", choice_expectation(r, aliases, false, ""))
445            }
446            (KindExpected::Choices(l, l_aliases), KindExpected::Choices(r, r_aliases)) => {
447                let mut both: Vec<&str> = l.to_vec();
448                both.extend(r.iter().filter(|c| !l.contains(c)));
449                let mut aliases = l_aliases.to_vec();
450                aliases.extend(
451                    r_aliases
452                        .iter()
453                        .filter(|a| !l_aliases.iter().any(|seen| seen.alias == a.alias)),
454                );
455                choice_expectation(&both, &aliases, false, "")
456            }
457        }
458    }
459
460    /// This expectation on its own: the noun, or `one of "a", "b"`.
461    fn text(&self) -> String {
462        match self {
463            KindExpected::Noun(noun) => noun.clone(),
464            KindExpected::Choices(choices, aliases) => {
465                crate::match_arg::choice_expectation(choices, aliases, false, "")
466            }
467        }
468    }
469}
470
471/// What one side of an `Either` parameter accepts, as the generated wrapper
472/// knows it, for [`either_expectation`]. Not public API.
473#[cfg(feature = "either")]
474#[doc(hidden)]
475pub enum ExpectedArm<'a> {
476    /// The macro's text for a type it knows (`a single number`).
477    Known(&'a str),
478    /// A type the macro does not know: what its
479    /// [`TryFromSexp::__MX_EXPECTATION`] declares, else what the arm's own
480    /// error says it expected, else `noun`, the arm's `@param` noun (the
481    /// type's name in code format for a type without an R name).
482    Opaque {
483        /// `<T as TryFromSexp>::__MX_EXPECTATION`.
484        declared: Option<&'a str>,
485        /// The fallback name of the arm.
486        noun: &'a str,
487    },
488    /// An `Option<T>` or `Result<T, ()>` arm: `NULL or <T>`.
489    Nullable(&'a ExpectedArm<'a>),
490    /// A nested `Either`, matched with the two sides of its error.
491    Either(&'a ExpectedArm<'a>, &'a ExpectedArm<'a>),
492}
493
494#[cfg(feature = "either")]
495impl ExpectedArm<'_> {
496    /// This side's expectation against the error it gave, and whether
497    /// anything but a fallback noun named it.
498    fn resolve(&self, error: &SexpError) -> Option<(KindExpected, bool)> {
499        match self {
500            ExpectedArm::Known(text)
501            | ExpectedArm::Opaque {
502                declared: Some(text),
503                ..
504            } => Some((KindExpected::Noun((*text).to_string()), true)),
505            ExpectedArm::Opaque {
506                declared: None,
507                noun,
508            } => Some(match error.arm_expectation() {
509                Some(expected) => (expected, true),
510                None => (KindExpected::Noun((*noun).to_string()), false),
511            }),
512            ExpectedArm::Nullable(inner) => inner.resolve(error).map(|(expected, known)| {
513                (
514                    KindExpected::Noun(format!("NULL or {}", expected.text())),
515                    known,
516                )
517            }),
518            ExpectedArm::Either(left, right) => {
519                let SexpError::EitherConversion {
520                    left_error,
521                    right_error,
522                } = error
523                else {
524                    return None;
525                };
526                let (l, l_known) = left.resolve(left_error)?;
527                let (r, r_known) = right.resolve(right_error)?;
528                Some((KindExpected::Noun(l.join(&r)), l_known || r_known))
529            }
530        }
531    }
532}
533
534/// Internal: the expectation of an `Either` parameter whose conversion failed
535/// with `error`, from what the wrapper knows of each side (`arms`), for
536/// `'<p>' must be <expected>: <reason>`. Not public API.
537///
538/// Each side says what its type accepts whatever the input, so the text stays
539/// the same for every failure of the parameter: `a single number or a data
540/// frame` for `Either<Dose, DataFrame>`, where `Dose` is a
541/// `#[derive(TryFromSexp)]` newtype of `AsNumeric`. A side whose type says
542/// nothing is named by its own error when that knows (`numeric` for a type
543/// error, the choices of a `match_arg` enum), else by its fallback noun. `None`
544/// when no side is named by more than that noun: the wrapper then reports
545/// `invalid '<p>' argument`.
546#[cfg(feature = "either")]
547#[doc(hidden)]
548pub fn either_expectation(error: &SexpError, arms: &ExpectedArm<'_>) -> Option<String> {
549    match arms.resolve(error)? {
550        (expected, true) => Some(expected.text()),
551        (_, false) => None,
552    }
553}
554
555impl SexpError {
556    /// What the value should have been, in R terms, when the error itself
557    /// knows and the argument's Rust type does not tell the macro (#1594): a
558    /// `match_arg` choice (`one of "fast", "slow"`), a class (`a data
559    /// frame`), or an `Either` whose arms both refused the kind of value
560    /// (`one of "fast", "slow", or numeric`). The generated wrapper then
561    /// writes `'<p>' must be <this>: <reason>` instead of
562    /// `invalid '<p>' argument: <reason>`. A type error alone gives none: the
563    /// `SEXPTYPE` it expected is the storage the conversion reads, which for
564    /// an opaque type need not be all it accepts.
565    pub(crate) fn r_expectation(&self) -> Option<String> {
566        match self {
567            SexpError::MatchArg(e) => Some(e.expectation()),
568            SexpError::Class(e) => Some(e.expected.to_string()),
569            #[cfg(feature = "either")]
570            SexpError::EitherConversion {
571                left_error,
572                right_error,
573            } => both_kind_mismatches(left_error, right_error).map(|(expected, _)| expected),
574            _ => None,
575        }
576    }
577
578    /// What this error expected and the value's `SEXPTYPE`, when it means the
579    /// value was not of the conversion's kind at all: a type error, a class
580    /// error, a `match_arg` refusal of a value that is not a string or factor,
581    /// or an `Either` whose arms both mean that. `None` when the value was of
582    /// the right kind and failed later (length, NA, value, a choice that did
583    /// not match).
584    #[cfg(feature = "either")]
585    fn kind_mismatch(&self) -> Option<(KindExpected, SEXPTYPE)> {
586        match self {
587            SexpError::Type(e) => Some((
588                KindExpected::Noun(crate::typed_list::sexptype_name(e.expected)),
589                e.actual,
590            )),
591            SexpError::Class(e) => Some((KindExpected::Noun(e.expected.to_string()), e.actual)),
592            SexpError::MatchArg(crate::match_arg::MatchArgError::InvalidType {
593                actual,
594                choices,
595                aliases,
596            }) => Some((KindExpected::Choices(choices, aliases), *actual)),
597            SexpError::EitherConversion {
598                left_error,
599                right_error,
600            } => both_kind_mismatches(left_error, right_error)
601                .map(|(expected, actual)| (KindExpected::Noun(expected), actual)),
602            _ => None,
603        }
604    }
605
606    /// What this error says one `Either` side expected, whether or not the
607    /// value was of that side's kind: the kind it refused ([`Self::kind_mismatch`]),
608    /// or the choices of a `match_arg` refusal that got further (a string
609    /// that matched no choice). `None` for an error that names no
610    /// expectation (length, NA, an invalid value).
611    #[cfg(feature = "either")]
612    fn arm_expectation(&self) -> Option<KindExpected> {
613        match self {
614            SexpError::MatchArg(e) => Some(KindExpected::Choices(e.choices(), e.aliases())),
615            _ => self.kind_mismatch().map(|(expected, _)| expected),
616        }
617    }
618
619    /// The reason of this conversion error in R terms, the text after
620    /// `'<p>' must be <expected>: ` in an argument error (#1591). `Display`
621    /// is written for the package author (SEXPTYPE names, `invalid value: `);
622    /// this says what was wrong with the value:
623    ///
624    /// | error | `expected_known` | reason |
625    /// |-------|------------------|--------|
626    /// | type | yes | `got character` |
627    /// | type | no | `expected integer, got character` |
628    /// | class | yes | `got list` |
629    /// | class | no | `expected a data frame, got list` |
630    /// | length | yes | `got length 2` |
631    /// | length | no | `expected length 1, got length 2` |
632    /// | NA | either | `NA is not allowed` |
633    /// | invalid value | either | the value's own text, without `invalid value: ` |
634    /// | missing field / duplicate name | either | `missing field 'x'` / `duplicate name 'x'` |
635    /// | `match_arg` | yes | `got "zzz"`, `got numeric`, `got length 2`, `NA is not allowed` |
636    /// | `match_arg` | no | `expected one of "a", "b", got "zzz"` |
637    /// | a check's refusal ([`SexpError::Condition`]) | either | its own message |
638    /// | `Either` | either | see below |
639    ///
640    /// `expected_known` says whether the text before the reason already
641    /// states what the value should be (`'x' must be a single integer`); the
642    /// reason then does not repeat it. A per-element reason inside a batched
643    /// message is worded with `expected_known = false`.
644    ///
645    /// Both branches of an `Either` failed. A type or class error, or a
646    /// `match_arg` refusal of a value that is not a string or factor, means
647    /// the value was not of that branch's kind at all; any other error means
648    /// it was, and failed later (length, NA, value), which is the more useful
649    /// reason. So: when both branches refused the kind, one reason for the
650    /// value's type (`got numeric`, or `expected integer or character, got
651    /// numeric`, `expected one of "a", "b", or numeric, got logical`); when
652    /// exactly one did, the other branch's reason; otherwise both branches'
653    /// reasons, once if they agree, else joined with `; `.
654    pub(crate) fn r_reason(&self, expected_known: bool) -> String {
655        match self {
656            SexpError::Type(e) => e.r_reason(expected_known),
657            SexpError::Class(e) => e.r_reason(expected_known),
658            SexpError::Length(e) => e.r_reason(expected_known),
659            SexpError::Na(_) => "NA is not allowed".to_string(),
660            SexpError::InvalidValue(msg) => msg.clone(),
661            SexpError::MissingField(name) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("missing field \'{0}\'", name))
    })format!("missing field '{name}'"),
662            SexpError::DuplicateName(name) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("duplicate name \'{0}\'", name))
    })format!("duplicate name '{name}'"),
663            SexpError::MatchArg(e) => e.r_reason(expected_known),
664            SexpError::Condition(e) => e.message_str().to_string(),
665            #[cfg(feature = "either")]
666            SexpError::EitherConversion {
667                left_error,
668                right_error,
669            } => {
670                let (l, r) = (left_error.as_ref(), right_error.as_ref());
671                if let Some((expected, actual)) = both_kind_mismatches(l, r) {
672                    let got = crate::typed_list::sexptype_name(actual);
673                    if expected_known {
674                        format!("got {got}")
675                    } else {
676                        format!("expected {expected}, got {got}")
677                    }
678                } else if l.kind_mismatch().is_some() {
679                    r.r_reason(expected_known)
680                } else if r.kind_mismatch().is_some() {
681                    l.r_reason(expected_known)
682                } else {
683                    let (l, r) = (l.r_reason(expected_known), r.r_reason(expected_known));
684                    if l == r { l } else { format!("{l}; {r}") }
685                }
686            }
687        }
688    }
689
690    /// The [`SexpError::Condition`] refusals whose reasons
691    /// [`r_reason`](Self::r_reason) reports, left arm first: the error itself
692    /// when it is one, and inside an `Either` the refusals of the arm (or
693    /// arms) whose reason is reported. Their classes and fields go on the
694    /// argument error.
695    pub(crate) fn reported_conditions(&self) -> Vec<&crate::condition::RError> {
696        match self {
697            SexpError::Condition(e) => ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [e]))vec![e],
698            #[cfg(feature = "either")]
699            SexpError::EitherConversion {
700                left_error,
701                right_error,
702            } => {
703                let (l, r) = (left_error.as_ref(), right_error.as_ref());
704                if both_kind_mismatches(l, r).is_some() {
705                    Vec::new()
706                } else if l.kind_mismatch().is_some() {
707                    r.reported_conditions()
708                } else if r.kind_mismatch().is_some() {
709                    l.reported_conditions()
710                } else {
711                    let mut both = l.reported_conditions();
712                    both.extend(r.reported_conditions());
713                    both
714                }
715            }
716            _ => Vec::new(),
717        }
718    }
719}
720
721/// The joined expectation of an `Either` whose two branches both refused the
722/// kind of value, with the value's `SEXPTYPE`; `None` if either branch got
723/// further (see [`SexpError::r_reason`]).
724#[cfg(feature = "either")]
725fn both_kind_mismatches(left: &SexpError, right: &SexpError) -> Option<(String, SEXPTYPE)> {
726    let (l, actual) = left.kind_mismatch()?;
727    let (r, _) = right.kind_mismatch()?;
728    Some((l.join(&r), actual))
729}
730
731impl std::fmt::Display for SexpError {
732    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
733        match self {
734            SexpError::Type(e) => f.write_fmt(format_args!("{0}", e))write!(f, "{}", e),
735            SexpError::Class(e) => f.write_fmt(format_args!("{0}", e))write!(f, "{}", e),
736            SexpError::Length(e) => f.write_fmt(format_args!("{0}", e))write!(f, "{}", e),
737            SexpError::Na(e) => f.write_fmt(format_args!("{0}", e))write!(f, "{}", e),
738            SexpError::InvalidValue(msg) => f.write_fmt(format_args!("invalid value: {0}", msg))write!(f, "invalid value: {}", msg),
739            SexpError::MissingField(name) => f.write_fmt(format_args!("missing field: {0}", name))write!(f, "missing field: {}", name),
740            SexpError::DuplicateName(name) => f.write_fmt(format_args!("duplicate name in list: {0:?}", name))write!(f, "duplicate name in list: {:?}", name),
741            SexpError::MatchArg(e) => f.write_fmt(format_args!("{0}", e))write!(f, "{}", e),
742            SexpError::Condition(e) => f.write_fmt(format_args!("{0}", e))write!(f, "{}", e),
743            #[cfg(feature = "either")]
744            SexpError::EitherConversion {
745                left_error,
746                right_error,
747            } => write!(
748                f,
749                "failed to convert to Either: Left failed ({}), Right failed ({})",
750                left_error, right_error
751            ),
752        }
753    }
754}
755
756impl std::error::Error for SexpError {
757    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
758        match self {
759            SexpError::Type(e) => Some(e),
760            SexpError::Class(e) => Some(e),
761            SexpError::Length(e) => Some(e),
762            SexpError::Na(e) => Some(e),
763            SexpError::InvalidValue(_) => None,
764            SexpError::MissingField(_) => None,
765            SexpError::DuplicateName(_) => None,
766            SexpError::MatchArg(e) => Some(e),
767            // `RError` does not implement `Error` (see its docs).
768            SexpError::Condition(_) => None,
769            #[cfg(feature = "either")]
770            SexpError::EitherConversion { .. } => None,
771        }
772    }
773}
774
775impl From<SexpTypeError> for SexpError {
776    fn from(e: SexpTypeError) -> Self {
777        SexpError::Type(e)
778    }
779}
780
781impl From<SexpClassError> for SexpError {
782    fn from(e: SexpClassError) -> Self {
783        SexpError::Class(e)
784    }
785}
786
787impl From<SexpLengthError> for SexpError {
788    fn from(e: SexpLengthError) -> Self {
789        SexpError::Length(e)
790    }
791}
792
793impl From<SexpNaError> for SexpError {
794    fn from(e: SexpNaError) -> Self {
795        SexpError::Na(e)
796    }
797}
798
799pub mod borrow;
800pub use borrow::NativeBorrow;
801
802/// TryFrom-style trait for converting SEXP to Rust types.
803///
804/// Inbound counterpart of [`crate::into_r::IntoR`]. Strict by construction
805/// (returns `Result`) — for a looser, multi-source coercion path use
806/// [`crate::coerce::Coerce`] / [`crate::coerce::TryCoerce`].
807///
808/// # Examples
809///
810/// ```no_run
811/// use miniextendr_api::SEXP;
812/// use miniextendr_api::from_r::TryFromSexp;
813///
814/// fn example(sexp: SEXP) {
815///     let value: i32 = TryFromSexp::try_from_sexp(sexp).unwrap();
816///     let text: String = TryFromSexp::try_from_sexp(sexp).unwrap();
817/// }
818/// ```
819pub trait TryFromSexp: Sized {
820    /// Native vector storage retained by this conversion, if any.
821    ///
822    /// Generated wrappers use this before conversion to reject aliased mutable
823    /// borrows. Owned and custom object conversions default to no native borrow.
824    /// Conversions that retain native references must describe their leaf and
825    /// forward this metadata through their containers.
826    const NATIVE_BORROW: Option<NativeBorrow> = None;
827
828    /// Whether this conversion accepts only character input: every value it
829    /// converts, other than `NULL`, is a character vector or a factor.
830    ///
831    /// `#[miniextendr]` reads it for choice parameters (`match_arg` /
832    /// `choices`) typed `Either<T, R>`. Their R wrapper sends all character
833    /// and factor input to the choice `T`, so an `R` with
834    /// `CHARACTER_ONLY = true` could receive at most `NULL`, and the macro
835    /// rejects the signature at compile time.
836    ///
837    /// Defaults to `false`, which is always safe: the check is skipped. Set it
838    /// to `true` in an implementation that reads only strings or factors, and
839    /// forward it (`<Inner as TryFromSexp>::CHARACTER_ONLY`) in one that
840    /// delegates to another conversion. Do not set it on a type that also
841    /// converts numbers, logicals or lists: that would reject valid
842    /// parameters.
843    ///
844    /// The built-in string and factor conversions (including the types given
845    /// [`try_from_sexp_via_str_parse!`](crate::try_from_sexp_via_str_parse),
846    /// such as uuid, url, regex and num-bigint), `#[derive(MatchArg)]` and
847    /// `#[derive(RFactor)]` set it. `Option<T>`, `Vec<T>`, `Box<[T]>`,
848    /// [`Missing<T>`](crate::Missing) and `#[derive(TryFromSexp)]` newtypes
849    /// forward it. [`AsCharacter`](crate::AsCharacter) leaves it `false`,
850    /// since it also converts numbers.
851    ///
852    /// # Examples
853    ///
854    /// ```no_run
855    /// use miniextendr_api::SEXP;
856    /// use miniextendr_api::from_r::{SexpError, TryFromSexp};
857    ///
858    /// pub struct Username(String);
859    ///
860    /// impl TryFromSexp for Username {
861    ///     type Error = SexpError;
862    ///     const CHARACTER_ONLY: bool = <String as TryFromSexp>::CHARACTER_ONLY;
863    ///
864    ///     fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
865    ///         String::try_from_sexp(sexp).map(Username)
866    ///     }
867    /// }
868    /// ```
869    const CHARACTER_ONLY: bool = false;
870
871    /// What an argument of this type must be, in R terms, when
872    /// `#[miniextendr]` cannot tell from the type as written: the
873    /// `<expected>` of a failed conversion's `'<p>' must be <expected>: ...`.
874    ///
875    /// `#[derive(TryFromSexp)]` sets it from the newtype's inner type
876    /// (`struct Dose(AsNumeric)` says `a single number`, as `AsNumeric`
877    /// does) or forwards the inner type's. The generated wrapper reads it on
878    /// the failure path of a parameter whose type the macro does not know,
879    /// alone or as an arm of an `Either`. `None` (the default) leaves the
880    /// expectation to the conversion error, else `invalid '<p>' argument`.
881    #[doc(hidden)]
882    const __MX_EXPECTATION: Option<&'static str> = None;
883
884    /// The error type returned when conversion fails.
885    type Error;
886
887    /// Attempt to convert an R SEXP to this Rust type.
888    ///
889    /// In debug builds, may assert that we're on R's main thread.
890    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error>;
891
892    /// Convert from SEXP without thread safety checks.
893    ///
894    /// # Safety
895    ///
896    /// Must be called from R's main thread. In debug builds, this still
897    /// calls the checked version by default, but implementations may
898    /// skip thread assertions for performance.
899    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
900        // Default: just call the checked version
901        Self::try_from_sexp(sexp)
902    }
903
904    /// Convert with a scope able to retain uncached ALTREP elements.
905    /// Custom reference converters keep their existing behavior by default.
906    ///
907    /// # Safety
908    /// R's main thread. Any R-backed references in the result must be narrowed
909    /// to the lifetime of `root`; this hook does not change `Self`'s lifetime.
910    #[doc(hidden)]
911    unsafe fn __mx_try_from_rooted(root: crate::Root<'_>) -> Result<Self, Self::Error> {
912        Self::try_from_sexp(root.get())
913    }
914
915    /// Whether the converted argument holds a value `#[miniextendr(no_na)]`
916    /// refuses after the conversion.
917    ///
918    /// The default is `false`, because `no_na`'s R guard (`!anyNA(x)`) already
919    /// refuses R's `NA` before the conversion runs. The reading markers
920    /// (`AsNumeric*`, `AsCharacter*`) override it: they read more inputs as
921    /// missing than `anyNA()` sees (the text `"NA"`, blank strings, a factor
922    /// `NA` level, ...). The layers a marker can sit in (`Option`, `Missing`,
923    /// the named-list maps, `Result<T, ()>`, `Either`, `#[derive(TryFromSexp)]`
924    /// newtypes) forward it, so a type alias or a newtype of a marker is
925    /// checked too. The generated C wrapper calls it on the value of every
926    /// `no_na` parameter that has the R guard.
927    #[doc(hidden)]
928    #[inline]
929    fn __mx_has_na(&self) -> bool {
930        false
931    }
932
933    /// Whether `#[miniextendr(no_na)]` refuses this converted argument, read
934    /// from `input` (the R value it converted from), when no R guard ran.
935    ///
936    /// An `Either` parameter gets no `!anyNA(x)` guard: one test on the whole
937    /// argument is wrong for an arm with its own reading of `NA`, such as a
938    /// data frame whose cells may be missing. Its C wrapper calls this instead
939    /// of [`__mx_has_na`](Self::__mx_has_na). The default makes the guard's
940    /// check in Rust (R's `anyNA(input)`), then asks `__mx_has_na` for what a
941    /// marker reads as `NA` beyond it. `Either` asks the arm the value
942    /// converted to, `Missing` asks its value, and `DataFrame` refuses
943    /// nothing, so the NA cells of a data frame arm get through. The wrappers
944    /// `Result<T, ()>`, `Option<DataFrame>`, `Option<Either<L, R>>` and
945    /// `#[derive(TryFromSexp)]` newtypes ask their value too, so a
946    /// `Result<DataFrame, ()>` or `Option<DataFrame>` arm, a newtype of a
947    /// `DataFrame`, or the data frame arm of an `Option<Either<..>>` keeps its
948    /// NA cells as well. Their `Err(())` / `None` comes only from `NULL`,
949    /// which holds no `NA`, so it passes.
950    ///
951    /// An `Option` arm over a scalar (`Either<Option<AsNumeric>, DataFrame>`)
952    /// keeps the default, which reads the input rather than the value: `NULL`
953    /// holds no `NA` and passes as `None`, while an `NA` the arm reads as
954    /// `None` (`Option<f64>`, `Option<String>`) is still refused. An override
955    /// that answered `false` for `None` would let that `NA` through; the
956    /// overrides above can, since their `None` is never an `NA`.
957    #[doc(hidden)]
958    #[inline]
959    fn __mx_input_has_na(&self, input: SEXP) -> bool {
960        any_na(input) || self.__mx_has_na()
961    }
962
963    /// Convert, running `check`, an element type's
964    /// [`TryFromSexpElement::check_sexp`](crate::TryFromSexpElement::check_sexp),
965    /// on the R value that carries an element's class (#1837).
966    ///
967    /// The newtype containers read `Vec<T>` through `Vec<T::Inner>` and
968    /// `Vec<Option<T>>` through `Vec<Option<T::Inner>>` with this, passing
969    /// `T::check_sexp`, so the inner container decides where the check runs:
970    ///
971    /// - The default runs it once, on the whole input, then converts. An
972    ///   atomic vector carries its class on the vector.
973    /// - A list of R objects runs it on each element instead, where each
974    ///   object carries its own class: `Vec<List>` on every element,
975    ///   `Vec<Option<List>>` on every element but `NULL`.
976    /// - The newtype containers pass it on, with their own element's check
977    ///   after it, so a newtype of a newtype checks outer then inner, as its
978    ///   scalar does.
979    ///
980    /// An override must also override
981    /// [`__mx_try_from_sexp_unchecked_with_check`](Self::__mx_try_from_sexp_unchecked_with_check).
982    /// Not public API.
983    #[doc(hidden)]
984    #[inline]
985    fn __mx_try_from_sexp_with_check(
986        sexp: SEXP,
987        check: &ElementCheck<'_>,
988    ) -> Result<Self, SexpError>
989    where
990        Self::Error: Into<SexpError>,
991    {
992        check(sexp)?;
993        Self::try_from_sexp(sexp).map_err(Into::into)
994    }
995
996    /// [`__mx_try_from_sexp_with_check`](Self::__mx_try_from_sexp_with_check)
997    /// through [`try_from_sexp_unchecked`](Self::try_from_sexp_unchecked).
998    /// Not public API.
999    ///
1000    /// # Safety
1001    ///
1002    /// As [`try_from_sexp_unchecked`](Self::try_from_sexp_unchecked).
1003    #[doc(hidden)]
1004    #[inline]
1005    unsafe fn __mx_try_from_sexp_unchecked_with_check(
1006        sexp: SEXP,
1007        check: &ElementCheck<'_>,
1008    ) -> Result<Self, SexpError>
1009    where
1010        Self::Error: Into<SexpError>,
1011    {
1012        check(sexp)?;
1013        unsafe { Self::try_from_sexp_unchecked(sexp) }.map_err(Into::into)
1014    }
1015}
1016
1017/// An element type's check on an R value, as the newtype containers pass it
1018/// to [`TryFromSexp::__mx_try_from_sexp_with_check`]: `T::check_sexp`, with
1019/// the checks of any enclosing newtypes before it. Not public API.
1020#[doc(hidden)]
1021pub type ElementCheck<'a> = dyn Fn(SEXP) -> Result<(), SexpError> + 'a;
1022
1023/// The [`ElementCheck`] that accepts every value: what a container starts
1024/// from when nothing encloses it. Not public API.
1025#[doc(hidden)]
1026#[inline]
1027pub fn no_element_check(_sexp: SEXP) -> Result<(), SexpError> {
1028    Ok(())
1029}
1030
1031// region: Box<[T]> delegates to Vec<T>
1032//
1033// A boxed slice converts exactly like the owned vector — read the vector, then
1034// `into_boxed_slice()` (an O(1), allocation-free shrink). This single blanket
1035// replaces every hand-rolled / macro-generated `Box<[X]>` impl: any element
1036// type whose `Vec<X>` is convertible gets `Box<[X]>` for free, inheriting the
1037// vector impl's error type and NA semantics by construction.
1038
1039impl<T> TryFromSexp for Box<[T]>
1040where
1041    Vec<T>: TryFromSexp,
1042{
1043    const NATIVE_BORROW: Option<NativeBorrow> = <Vec<T> as TryFromSexp>::NATIVE_BORROW;
1044    const CHARACTER_ONLY: bool = <Vec<T> as TryFromSexp>::CHARACTER_ONLY;
1045
1046    type Error = <Vec<T> as TryFromSexp>::Error;
1047
1048    #[inline]
1049    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1050        <Vec<T> as TryFromSexp>::try_from_sexp(sexp).map(|v| v.into_boxed_slice())
1051    }
1052
1053    #[inline]
1054    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1055        unsafe { <Vec<T> as TryFromSexp>::try_from_sexp_unchecked(sexp) }
1056            .map(|v| v.into_boxed_slice())
1057    }
1058}
1059// endregion
1060
1061macro_rules! impl_try_from_sexp_scalar_native {
1062    ($t:ty, $sexptype:ident) => {
1063        impl TryFromSexp for $t {
1064            type Error = SexpError;
1065
1066            #[inline]
1067            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1068                let actual = sexp.type_of();
1069                if actual != SEXPTYPE::$sexptype {
1070                    return Err(SexpTypeError {
1071                        expected: SEXPTYPE::$sexptype,
1072                        actual,
1073                    }
1074                    .into());
1075                }
1076                let len = sexp.len();
1077                if len != 1 {
1078                    return Err(SexpLengthError {
1079                        expected: 1,
1080                        actual: len,
1081                    }
1082                    .into());
1083                }
1084                unsafe { sexp.as_slice::<$t>() }
1085                    .first()
1086                    .cloned()
1087                    .ok_or_else(|| {
1088                        SexpLengthError {
1089                            expected: 1,
1090                            actual: 0,
1091                        }
1092                        .into()
1093                    })
1094            }
1095
1096            #[inline]
1097            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1098                let actual = sexp.type_of();
1099                if actual != SEXPTYPE::$sexptype {
1100                    return Err(SexpTypeError {
1101                        expected: SEXPTYPE::$sexptype,
1102                        actual,
1103                    }
1104                    .into());
1105                }
1106                let len = unsafe { sexp.len_unchecked() };
1107                if len != 1 {
1108                    return Err(SexpLengthError {
1109                        expected: 1,
1110                        actual: len,
1111                    }
1112                    .into());
1113                }
1114                unsafe { sexp.as_slice_unchecked::<$t>() }
1115                    .first()
1116                    .cloned()
1117                    .ok_or_else(|| {
1118                        SexpLengthError {
1119                            expected: 1,
1120                            actual: 0,
1121                        }
1122                        .into()
1123                    })
1124            }
1125        }
1126    };
1127}
1128
1129// i32 has a bespoke impl that checks for NA_integer_ (i32::MIN).
1130// The shared macro is NOT used for i32 — it would silently pass NA through.
1131impl TryFromSexp for i32 {
1132    type Error = SexpError;
1133
1134    #[inline]
1135    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1136        let actual = sexp.type_of();
1137        if actual != SEXPTYPE::INTSXP {
1138            return Err(SexpTypeError {
1139                expected: SEXPTYPE::INTSXP,
1140                actual,
1141            }
1142            .into());
1143        }
1144        let len = sexp.len();
1145        if len != 1 {
1146            return Err(SexpLengthError {
1147                expected: 1,
1148                actual: len,
1149            }
1150            .into());
1151        }
1152        let v = unsafe { sexp.as_slice::<i32>() }
1153            .first()
1154            .cloned()
1155            .ok_or_else(|| {
1156                SexpError::from(SexpLengthError {
1157                    expected: 1,
1158                    actual: 0,
1159                })
1160            })?;
1161        if v.is_na() {
1162            return Err(SexpNaError {
1163                sexp_type: SEXPTYPE::INTSXP,
1164            }
1165            .into());
1166        }
1167        Ok(v)
1168    }
1169
1170    #[inline]
1171    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1172        let actual = sexp.type_of();
1173        if actual != SEXPTYPE::INTSXP {
1174            return Err(SexpTypeError {
1175                expected: SEXPTYPE::INTSXP,
1176                actual,
1177            }
1178            .into());
1179        }
1180        let len = unsafe { sexp.len_unchecked() };
1181        if len != 1 {
1182            return Err(SexpLengthError {
1183                expected: 1,
1184                actual: len,
1185            }
1186            .into());
1187        }
1188        let v = unsafe { sexp.as_slice_unchecked::<i32>() }
1189            .first()
1190            .cloned()
1191            .ok_or_else(|| {
1192                SexpError::from(SexpLengthError {
1193                    expected: 1,
1194                    actual: 0,
1195                })
1196            })?;
1197        if v.is_na() {
1198            return Err(SexpNaError {
1199                sexp_type: SEXPTYPE::INTSXP,
1200            }
1201            .into());
1202        }
1203        Ok(v)
1204    }
1205}
1206
1207impl TryFromSexp for f64 {
    type Error = SexpError;
    #[inline]
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let actual = sexp.type_of();
        if actual != SEXPTYPE::REALSXP {
            return Err(SexpTypeError {
                            expected: SEXPTYPE::REALSXP,
                            actual,
                        }.into());
        }
        let len = sexp.len();
        if len != 1 {
            return Err(SexpLengthError { expected: 1, actual: len }.into());
        }
        unsafe {
                        sexp.as_slice::<f64>()
                    }.first().cloned().ok_or_else(||
                { SexpLengthError { expected: 1, actual: 0 }.into() })
    }
    #[inline]
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        let actual = sexp.type_of();
        if actual != SEXPTYPE::REALSXP {
            return Err(SexpTypeError {
                            expected: SEXPTYPE::REALSXP,
                            actual,
                        }.into());
        }
        let len = unsafe { sexp.len_unchecked() };
        if len != 1 {
            return Err(SexpLengthError { expected: 1, actual: len }.into());
        }
        unsafe {
                        sexp.as_slice_unchecked::<f64>()
                    }.first().cloned().ok_or_else(||
                { SexpLengthError { expected: 1, actual: 0 }.into() })
    }
}impl_try_from_sexp_scalar_native!(f64, REALSXP);
1208impl TryFromSexp for u8 {
    type Error = SexpError;
    #[inline]
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let actual = sexp.type_of();
        if actual != SEXPTYPE::RAWSXP {
            return Err(SexpTypeError {
                            expected: SEXPTYPE::RAWSXP,
                            actual,
                        }.into());
        }
        let len = sexp.len();
        if len != 1 {
            return Err(SexpLengthError { expected: 1, actual: len }.into());
        }
        unsafe {
                        sexp.as_slice::<u8>()
                    }.first().cloned().ok_or_else(||
                { SexpLengthError { expected: 1, actual: 0 }.into() })
    }
    #[inline]
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        let actual = sexp.type_of();
        if actual != SEXPTYPE::RAWSXP {
            return Err(SexpTypeError {
                            expected: SEXPTYPE::RAWSXP,
                            actual,
                        }.into());
        }
        let len = unsafe { sexp.len_unchecked() };
        if len != 1 {
            return Err(SexpLengthError { expected: 1, actual: len }.into());
        }
        unsafe {
                        sexp.as_slice_unchecked::<u8>()
                    }.first().cloned().ok_or_else(||
                { SexpLengthError { expected: 1, actual: 0 }.into() })
    }
}impl_try_from_sexp_scalar_native!(u8, RAWSXP);
1209impl TryFromSexp for RLogical {
    type Error = SexpError;
    #[inline]
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let actual = sexp.type_of();
        if actual != SEXPTYPE::LGLSXP {
            return Err(SexpTypeError {
                            expected: SEXPTYPE::LGLSXP,
                            actual,
                        }.into());
        }
        let len = sexp.len();
        if len != 1 {
            return Err(SexpLengthError { expected: 1, actual: len }.into());
        }
        unsafe {
                        sexp.as_slice::<RLogical>()
                    }.first().cloned().ok_or_else(||
                { SexpLengthError { expected: 1, actual: 0 }.into() })
    }
    #[inline]
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        let actual = sexp.type_of();
        if actual != SEXPTYPE::LGLSXP {
            return Err(SexpTypeError {
                            expected: SEXPTYPE::LGLSXP,
                            actual,
                        }.into());
        }
        let len = unsafe { sexp.len_unchecked() };
        if len != 1 {
            return Err(SexpLengthError { expected: 1, actual: len }.into());
        }
        unsafe {
                        sexp.as_slice_unchecked::<RLogical>()
                    }.first().cloned().ok_or_else(||
                { SexpLengthError { expected: 1, actual: 0 }.into() })
    }
}impl_try_from_sexp_scalar_native!(RLogical, LGLSXP);
1210impl TryFromSexp for crate::Rcomplex {
    type Error = SexpError;
    #[inline]
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let actual = sexp.type_of();
        if actual != SEXPTYPE::CPLXSXP {
            return Err(SexpTypeError {
                            expected: SEXPTYPE::CPLXSXP,
                            actual,
                        }.into());
        }
        let len = sexp.len();
        if len != 1 {
            return Err(SexpLengthError { expected: 1, actual: len }.into());
        }
        unsafe {
                        sexp.as_slice::<crate::Rcomplex>()
                    }.first().cloned().ok_or_else(||
                { SexpLengthError { expected: 1, actual: 0 }.into() })
    }
    #[inline]
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        let actual = sexp.type_of();
        if actual != SEXPTYPE::CPLXSXP {
            return Err(SexpTypeError {
                            expected: SEXPTYPE::CPLXSXP,
                            actual,
                        }.into());
        }
        let len = unsafe { sexp.len_unchecked() };
        if len != 1 {
            return Err(SexpLengthError { expected: 1, actual: len }.into());
        }
        unsafe {
                        sexp.as_slice_unchecked::<crate::Rcomplex>()
                    }.first().cloned().ok_or_else(||
                { SexpLengthError { expected: 1, actual: 0 }.into() })
    }
}impl_try_from_sexp_scalar_native!(crate::Rcomplex, CPLXSXP);
1211
1212/// Pass-through conversion for raw SEXP values with ALTREP auto-materialization.
1213///
1214/// This allows `SEXP` to be used directly in `#[miniextendr]` function signatures.
1215/// When R passes an ALTREP vector (e.g., `1:10`, `seq_len(N)`),
1216/// [`ensure_materialized`](crate::altrep_sexp::ensure_materialized) is called
1217/// automatically to force materialization on the R main thread. After this,
1218/// the SEXP's data pointer is stable and safe to access from any thread.
1219///
1220/// # ALTREP handling
1221///
1222/// | Input | Result |
1223/// |---|---|
1224/// | Regular SEXP | Passed through unchanged |
1225/// | ALTREP SEXP | Materialized via `ensure_materialized`, then passed through |
1226///
1227/// To receive ALTREP without materializing, use
1228/// [`AltrepSexp`](crate::altrep_sexp::AltrepSexp) as the parameter type instead.
1229/// To receive the raw SEXP without any conversion (including no materialization),
1230/// use `extern "C-unwind"`.
1231///
1232/// # Safety
1233///
1234/// SEXP handles are only valid on R's main thread. Standalone `#[miniextendr]`
1235/// functions taking a `SEXP` parameter run on the main thread automatically.
1236impl TryFromSexp for SEXP {
1237    type Error = SexpError;
1238
1239    /// Converts a SEXP, auto-materializing ALTREP vectors.
1240    ///
1241    /// If the input is ALTREP, [`ensure_materialized`](crate::altrep_sexp::ensure_materialized)
1242    /// is called to force materialization on the R main thread. After
1243    /// materialization the data pointer is stable and the SEXP can be safely
1244    /// sent to other threads.
1245    #[inline]
1246    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1247        Ok(unsafe { crate::altrep_sexp::ensure_materialized(sexp) })
1248    }
1249
1250    #[inline]
1251    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1252        Ok(unsafe { crate::altrep_sexp::ensure_materialized(sexp) })
1253    }
1254}
1255
1256impl TryFromSexp for Option<SEXP> {
1257    type Error = SexpError;
1258
1259    #[inline]
1260    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1261        if sexp.type_of() == SEXPTYPE::NILSXP {
1262            Ok(None)
1263        } else {
1264            Ok(Some(unsafe {
1265                crate::altrep_sexp::ensure_materialized(sexp)
1266            }))
1267        }
1268    }
1269
1270    #[inline]
1271    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1272        Self::try_from_sexp(sexp)
1273    }
1274}
1275// endregion
1276
1277mod logical;
1278
1279mod coerced_scalars;
1280pub(crate) use coerced_scalars::{coerce_real_value, coerce_value};
1281
1282mod references;
1283
1284// region: Blanket implementations for slices with arbitrary lifetimes
1285
1286/// Blanket impl for `&[T]` where T: RNativeType
1287///
1288/// This replaces the macro-generated `&'static [T]` impls with a more composable
1289/// blanket impl that works for any lifetime. This enables containers like TinyVec
1290/// to use blanket impls without needing helper functions.
1291impl<T> TryFromSexp for &[T]
1292where
1293    T: crate::RNativeType + Copy,
1294{
1295    const NATIVE_BORROW: Option<NativeBorrow> = Some(NativeBorrow::slice(T::SEXP_TYPE, false));
1296
1297    type Error = SexpTypeError;
1298
1299    #[inline]
1300    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1301        let actual = sexp.type_of();
1302        if actual != T::SEXP_TYPE {
1303            return Err(SexpTypeError {
1304                expected: T::SEXP_TYPE,
1305                actual,
1306            });
1307        }
1308        Ok(unsafe { sexp.as_slice::<T>() })
1309    }
1310
1311    #[inline]
1312    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1313        let actual = sexp.type_of();
1314        if actual != T::SEXP_TYPE {
1315            return Err(SexpTypeError {
1316                expected: T::SEXP_TYPE,
1317                actual,
1318            });
1319        }
1320        Ok(unsafe { sexp.as_slice_unchecked::<T>() })
1321    }
1322}
1323
1324/// Blanket impl for `&mut [T]` where T: RNativeType
1325///
1326/// # Safety note (aliasing)
1327///
1328/// This impl hands out a `&mut` view over R's data pointer without copying, so
1329/// binding the same R vector to two slice parameters aliases one buffer. That is
1330/// undefined behavior whenever at least one borrow is mutable — two `&mut [T]`,
1331/// or a `&mut [T]` paired with a shared `&[T]` (which borrows the same buffer).
1332/// Generated wrappers reject repeated non-empty R vector identities before
1333/// conversion in every build, including scalar/slice pairs and borrowed leaves
1334/// inside optional, nested, and boxed containers (#1104, #1252, #1502). Direct callers of this conversion must ensure the
1335/// resulting borrow does not overlap another live mutable or shared borrow.
1336impl<T> TryFromSexp for &mut [T]
1337where
1338    T: crate::RNativeType + Copy,
1339{
1340    const NATIVE_BORROW: Option<NativeBorrow> = Some(NativeBorrow::slice(T::SEXP_TYPE, true));
1341
1342    type Error = SexpTypeError;
1343
1344    #[inline]
1345    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1346        let actual = sexp.type_of();
1347        if actual != T::SEXP_TYPE {
1348            return Err(SexpTypeError {
1349                expected: T::SEXP_TYPE,
1350                actual,
1351            });
1352        }
1353        let len = sexp.len();
1354        let ptr = unsafe { T::dataptr_mut(sexp) };
1355        Ok(unsafe { r_slice_mut(ptr, len) })
1356    }
1357
1358    #[inline]
1359    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1360        let actual = sexp.type_of();
1361        if actual != T::SEXP_TYPE {
1362            return Err(SexpTypeError {
1363                expected: T::SEXP_TYPE,
1364                actual,
1365            });
1366        }
1367        let len = unsafe { sexp.len_unchecked() };
1368        let ptr = unsafe { T::dataptr_mut(sexp) };
1369        Ok(unsafe { r_slice_mut(ptr, len) })
1370    }
1371}
1372
1373/// Blanket impl for `Option<&[T]>` where T: RNativeType
1374impl<T> TryFromSexp for Option<&[T]>
1375where
1376    T: crate::RNativeType + Copy,
1377{
1378    const NATIVE_BORROW: Option<NativeBorrow> = <&[T] as TryFromSexp>::NATIVE_BORROW;
1379
1380    type Error = SexpError;
1381
1382    #[inline]
1383    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1384        if sexp.type_of() == SEXPTYPE::NILSXP {
1385            return Ok(None);
1386        }
1387        let slice: &[T] = TryFromSexp::try_from_sexp(sexp).map_err(SexpError::from)?;
1388        Ok(Some(slice))
1389    }
1390
1391    #[inline]
1392    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1393        if sexp.type_of() == SEXPTYPE::NILSXP {
1394            return Ok(None);
1395        }
1396        let slice: &[T] =
1397            unsafe { TryFromSexp::try_from_sexp_unchecked(sexp).map_err(SexpError::from)? };
1398        Ok(Some(slice))
1399    }
1400}
1401
1402/// Blanket impl for `Option<&mut [T]>` where T: RNativeType
1403impl<T> TryFromSexp for Option<&mut [T]>
1404where
1405    T: crate::RNativeType + Copy,
1406{
1407    const NATIVE_BORROW: Option<NativeBorrow> = <&mut [T] as TryFromSexp>::NATIVE_BORROW;
1408
1409    type Error = SexpError;
1410
1411    #[inline]
1412    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1413        if sexp.type_of() == SEXPTYPE::NILSXP {
1414            return Ok(None);
1415        }
1416        let slice: &mut [T] = TryFromSexp::try_from_sexp(sexp).map_err(SexpError::from)?;
1417        Ok(Some(slice))
1418    }
1419
1420    #[inline]
1421    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1422        if sexp.type_of() == SEXPTYPE::NILSXP {
1423            return Ok(None);
1424        }
1425        let slice: &mut [T] =
1426            unsafe { TryFromSexp::try_from_sexp_unchecked(sexp).map_err(SexpError::from)? };
1427        Ok(Some(slice))
1428    }
1429}
1430// endregion
1431
1432mod strings;
1433
1434mod str_parse;
1435pub use str_parse::{ParseRStr, ParsedRStr};
1436
1437// region: Result conversions (NULL -> Err(()))
1438
1439impl<T> TryFromSexp for Result<T, ()>
1440where
1441    T: TryFromSexp,
1442    T::Error: Into<SexpError>,
1443{
1444    const NATIVE_BORROW: Option<crate::from_r::NativeBorrow> = T::NATIVE_BORROW;
1445
1446    type Error = SexpError;
1447    // `NULL` (`Err(())`) is "not given" and passes `no_na`.
1448    #[inline]
1449    fn __mx_has_na(&self) -> bool {
1450        self.as_ref().is_ok_and(T::__mx_has_na)
1451    }
1452
1453    // As an `Either` arm, a given value is read by `T` (a
1454    // `Result<DataFrame, ()>` arm keeps its `NA` cells); `NULL` holds no `NA`.
1455    #[inline]
1456    fn __mx_input_has_na(&self, input: SEXP) -> bool {
1457        match self {
1458            Ok(v) => T::__mx_input_has_na(v, input),
1459            Err(()) => false,
1460        }
1461    }
1462
1463    #[inline]
1464    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1465        if sexp.type_of() == SEXPTYPE::NILSXP {
1466            return Ok(Err(()));
1467        }
1468        let value = T::try_from_sexp(sexp).map_err(Into::into)?;
1469        Ok(Ok(value))
1470    }
1471
1472    #[inline]
1473    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1474        if sexp.type_of() == SEXPTYPE::NILSXP {
1475            return Ok(Err(()));
1476        }
1477        let value = unsafe { T::try_from_sexp_unchecked(sexp).map_err(Into::into)? };
1478        Ok(Ok(value))
1479    }
1480}
1481// endregion
1482
1483mod na_vectors;
1484
1485mod collections;
1486
1487mod tuples;
1488
1489// region: Fixed-size array conversions
1490
1491/// Blanket impl: Convert R vector to `[T; N]` where T: RNativeType.
1492///
1493/// Returns an error if the R vector length doesn't match N.
1494/// Useful for SHA hashes ([u8; 32]), fixed-size patterns, etc.
1495impl<T, const N: usize> TryFromSexp for [T; N]
1496where
1497    T: crate::RNativeType + Copy,
1498{
1499    type Error = SexpError;
1500
1501    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1502        let slice: &[T] = TryFromSexp::try_from_sexp(sexp)?;
1503        if slice.len() != N {
1504            return Err(SexpLengthError {
1505                expected: N,
1506                actual: slice.len(),
1507            }
1508            .into());
1509        }
1510
1511        // T: Copy, length verified above. Use MaybeUninit + copy_from_slice.
1512        let mut arr = std::mem::MaybeUninit::<[T; N]>::uninit();
1513        unsafe {
1514            // SAFETY: MaybeUninit<[T; N]> and [T; N] have the same layout.
1515            // We write all N elements via copy_from_slice, so assume_init is safe.
1516            let dst: &mut [T] = std::slice::from_raw_parts_mut(arr.as_mut_ptr().cast::<T>(), N);
1517            dst.copy_from_slice(&slice[..N]);
1518            Ok(arr.assume_init())
1519        }
1520    }
1521
1522    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1523        Self::try_from_sexp(sexp)
1524    }
1525}
1526// endregion
1527
1528// region: VecDeque conversions
1529
1530use std::collections::VecDeque;
1531
1532/// Blanket impl: Convert R vector to `VecDeque<T>` where T: RNativeType.
1533impl<T> TryFromSexp for VecDeque<T>
1534where
1535    T: crate::RNativeType + Copy,
1536{
1537    type Error = SexpTypeError;
1538
1539    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1540        let slice: &[T] = TryFromSexp::try_from_sexp(sexp)?;
1541        Ok(VecDeque::from(slice.to_vec()))
1542    }
1543
1544    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1545        let slice: &[T] = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
1546        Ok(VecDeque::from(slice.to_vec()))
1547    }
1548}
1549// endregion
1550
1551// region: BinaryHeap conversions
1552
1553use std::collections::BinaryHeap;
1554
1555/// Blanket impl: Convert R vector to `BinaryHeap<T>` where T: RNativeType + Ord.
1556///
1557/// Creates a binary heap from the R vector elements.
1558impl<T> TryFromSexp for BinaryHeap<T>
1559where
1560    T: crate::RNativeType + Copy + Ord,
1561{
1562    type Error = SexpTypeError;
1563
1564    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1565        let slice: &[T] = TryFromSexp::try_from_sexp(sexp)?;
1566        Ok(BinaryHeap::from(slice.to_vec()))
1567    }
1568
1569    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1570        let slice: &[T] = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
1571        Ok(BinaryHeap::from(slice.to_vec()))
1572    }
1573}
1574// endregion
1575
1576mod cow_and_paths;
1577mod rooted;
1578pub use rooted::TryFromRoot;
1579
1580// region: Option<Collection> conversions
1581//
1582// These convert NULL → None, and non-NULL to Some(collection).
1583// This differs from Option<scalar> which converts NA → None.
1584
1585/// Convert R value to `Option<Vec<T>>`: NULL → None, otherwise Some(vec).
1586impl<T> TryFromSexp for Option<Vec<T>>
1587where
1588    Vec<T>: TryFromSexp,
1589    <Vec<T> as TryFromSexp>::Error: Into<SexpError>,
1590{
1591    const NATIVE_BORROW: Option<NativeBorrow> = <Vec<T> as TryFromSexp>::NATIVE_BORROW;
1592    const CHARACTER_ONLY: bool = <Vec<T> as TryFromSexp>::CHARACTER_ONLY;
1593
1594    type Error = SexpError;
1595
1596    #[inline]
1597    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1598        if sexp.type_of() == SEXPTYPE::NILSXP {
1599            Ok(None)
1600        } else {
1601            Vec::<T>::try_from_sexp(sexp).map(Some).map_err(Into::into)
1602        }
1603    }
1604
1605    #[inline]
1606    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1607        if sexp.type_of() == SEXPTYPE::NILSXP {
1608            Ok(None)
1609        } else {
1610            unsafe {
1611                Vec::<T>::try_from_sexp_unchecked(sexp)
1612                    .map(Some)
1613                    .map_err(Into::into)
1614            }
1615        }
1616    }
1617}
1618
1619macro_rules! impl_option_map_try_from_sexp {
1620    ($(#[$meta:meta])* $map_ty:ident) => {
1621        $(#[$meta])*
1622        impl<V: TryFromSexp> TryFromSexp for Option<$map_ty<String, V>>
1623        where
1624            V::Error: Into<SexpError>,
1625        {
1626            const NATIVE_BORROW: Option<NativeBorrow> =
1627                <$map_ty<String, V> as TryFromSexp>::NATIVE_BORROW;
1628
1629            type Error = SexpError;
1630            #[inline]
1631            fn __mx_has_na(&self) -> bool {
1632                self.as_ref()
1633                    .is_some_and(<$map_ty<String, V> as TryFromSexp>::__mx_has_na)
1634            }
1635
1636            #[inline]
1637            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1638                if sexp.type_of() == SEXPTYPE::NILSXP {
1639                    Ok(None)
1640                } else {
1641                    $map_ty::<String, V>::try_from_sexp(sexp).map(Some)
1642                }
1643            }
1644        }
1645    };
1646}
1647
1648#[doc =
r" Convert R value to `Option<HashMap<String, V>>`: NULL -> None, otherwise Some(map)."]
impl<V: TryFromSexp> TryFromSexp for Option<HashMap<String, V>> where
    V::Error: Into<SexpError> {
    const NATIVE_BORROW: Option<NativeBorrow> =
        <HashMap<String, V> as TryFromSexp>::NATIVE_BORROW;
    type Error = SexpError;
    #[inline]
    fn __mx_has_na(&self) -> bool {
        self.as_ref().is_some_and(<HashMap<String, V> as
                TryFromSexp>::__mx_has_na)
    }
    #[inline]
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        if sexp.type_of() == SEXPTYPE::NILSXP {
            Ok(None)
        } else { HashMap::<String, V>::try_from_sexp(sexp).map(Some) }
    }
}impl_option_map_try_from_sexp!(
1649    /// Convert R value to `Option<HashMap<String, V>>`: NULL -> None, otherwise Some(map).
1650    HashMap
1651);
1652#[doc =
r" Convert R value to `Option<BTreeMap<String, V>>`: NULL -> None, otherwise Some(map)."]
impl<V: TryFromSexp> TryFromSexp for Option<BTreeMap<String, V>> where
    V::Error: Into<SexpError> {
    const NATIVE_BORROW: Option<NativeBorrow> =
        <BTreeMap<String, V> as TryFromSexp>::NATIVE_BORROW;
    type Error = SexpError;
    #[inline]
    fn __mx_has_na(&self) -> bool {
        self.as_ref().is_some_and(<BTreeMap<String, V> as
                TryFromSexp>::__mx_has_na)
    }
    #[inline]
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        if sexp.type_of() == SEXPTYPE::NILSXP {
            Ok(None)
        } else { BTreeMap::<String, V>::try_from_sexp(sexp).map(Some) }
    }
}impl_option_map_try_from_sexp!(
1653    /// Convert R value to `Option<BTreeMap<String, V>>`: NULL -> None, otherwise Some(map).
1654    BTreeMap
1655);
1656
1657macro_rules! impl_option_set_try_from_sexp {
1658    ($(#[$meta:meta])* $set_ty:ident) => {
1659        $(#[$meta])*
1660        impl<T> TryFromSexp for Option<$set_ty<T>>
1661        where
1662            $set_ty<T>: TryFromSexp,
1663            <$set_ty<T> as TryFromSexp>::Error: Into<SexpError>,
1664        {
1665            const CHARACTER_ONLY: bool = <$set_ty<T> as TryFromSexp>::CHARACTER_ONLY;
1666
1667            type Error = SexpError;
1668
1669            #[inline]
1670            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1671                if sexp.type_of() == SEXPTYPE::NILSXP {
1672                    Ok(None)
1673                } else {
1674                    $set_ty::<T>::try_from_sexp(sexp)
1675                        .map(Some)
1676                        .map_err(Into::into)
1677                }
1678            }
1679        }
1680    };
1681}
1682
1683#[doc =
r" Convert R value to `Option<HashSet<T>>`: NULL -> None, otherwise Some(set)."]
impl<T> TryFromSexp for Option<HashSet<T>> where HashSet<T>: TryFromSexp,
    <HashSet<T> as TryFromSexp>::Error: Into<SexpError> {
    const CHARACTER_ONLY: bool = <HashSet<T> as TryFromSexp>::CHARACTER_ONLY;
    type Error = SexpError;
    #[inline]
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        if sexp.type_of() == SEXPTYPE::NILSXP {
            Ok(None)
        } else {
            HashSet::<T>::try_from_sexp(sexp).map(Some).map_err(Into::into)
        }
    }
}impl_option_set_try_from_sexp!(
1684    /// Convert R value to `Option<HashSet<T>>`: NULL -> None, otherwise Some(set).
1685    HashSet
1686);
1687#[doc =
r" Convert R value to `Option<BTreeSet<T>>`: NULL -> None, otherwise Some(set)."]
impl<T> TryFromSexp for Option<BTreeSet<T>> where BTreeSet<T>: TryFromSexp,
    <BTreeSet<T> as TryFromSexp>::Error: Into<SexpError> {
    const CHARACTER_ONLY: bool = <BTreeSet<T> as TryFromSexp>::CHARACTER_ONLY;
    type Error = SexpError;
    #[inline]
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        if sexp.type_of() == SEXPTYPE::NILSXP {
            Ok(None)
        } else {
            BTreeSet::<T>::try_from_sexp(sexp).map(Some).map_err(Into::into)
        }
    }
}impl_option_set_try_from_sexp!(
1688    /// Convert R value to `Option<BTreeSet<T>>`: NULL -> None, otherwise Some(set).
1689    BTreeSet
1690);
1691// endregion
1692
1693// region: Nested vector conversions (list of vectors)
1694
1695/// Convert R list (VECSXP) to `Vec<Vec<T>>`.
1696///
1697/// Each element of the R list must be convertible to `Vec<T>`.
1698impl<T> TryFromSexp for Vec<Vec<T>>
1699where
1700    Vec<T>: TryFromSexp,
1701    <Vec<T> as TryFromSexp>::Error: Into<SexpError>,
1702{
1703    const NATIVE_BORROW: Option<NativeBorrow> =
1704        NativeBorrow::in_list(<Vec<T> as TryFromSexp>::NATIVE_BORROW);
1705
1706    type Error = SexpError;
1707
1708    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1709        map_vecsxp_with(sexp, |_i, elem| {
1710            Vec::<T>::try_from_sexp(elem).map_err(Into::into)
1711        })
1712    }
1713
1714    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1715        unsafe {
1716            map_vecsxp_with_unchecked(sexp, |_i, elem| {
1717                Vec::<T>::try_from_sexp_unchecked(elem).map_err(Into::into)
1718            })
1719        }
1720    }
1721}
1722// endregion
1723
1724// region: Coerced wrapper - bridge between TryFromSexp and TryCoerce
1725
1726use crate::coerce::Coerced;
1727
1728/// Convert R value to `Coerced<T, R>` by reading `R` and coercing to `T`.
1729///
1730/// This enables reading non-native Rust types from R with coercion:
1731///
1732/// ```ignore
1733/// // Read i64 from R integer (i32)
1734/// let val: Coerced<i64, i32> = TryFromSexp::try_from_sexp(sexp)?;
1735/// let i64_val: i64 = val.into_inner();
1736///
1737/// // Works with collections too:
1738/// let vec: Vec<Coerced<i64, i32>> = ...;
1739/// let set: HashSet<Coerced<NonZeroU32, i32>> = ...;
1740/// ```
1741impl<T, R> TryFromSexp for Coerced<T, R>
1742where
1743    R: TryFromSexp,
1744    R: TryCoerce<T>,
1745    <R as TryFromSexp>::Error: Into<SexpError>,
1746    <R as TryCoerce<T>>::Error: std::fmt::Display,
1747{
1748    type Error = SexpError;
1749
1750    #[inline]
1751    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1752        let r_val: R = R::try_from_sexp(sexp).map_err(Into::into)?;
1753        let value: T = r_val
1754            .try_coerce()
1755            .map_err(|e| SexpError::InvalidValue(::alloc::__export::must_use({ ::alloc::fmt::format(format_args!("{0}", e)) })format!("{e}")))?;
1756        Ok(Coerced::new(value))
1757    }
1758
1759    #[inline]
1760    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1761        let r_val: R = unsafe { R::try_from_sexp_unchecked(sexp).map_err(Into::into)? };
1762        let value: T = r_val
1763            .try_coerce()
1764            .map_err(|e| SexpError::InvalidValue(::alloc::__export::must_use({ ::alloc::fmt::format(format_args!("{0}", e)) })format!("{e}")))?;
1765        Ok(Coerced::new(value))
1766    }
1767}
1768// endregion
1769
1770/// Convert a `coerce` boolean parameter from a logical or integer zero/one.
1771/// Numeric parameters already have multi-source `TryFromSexp` implementations;
1772/// bool needs this extension while its ordinary conversion stays logical-only.
1773#[doc(hidden)]
1774pub fn try_from_sexp_coerced_bool(sexp: SEXP) -> Result<bool, SexpError> {
1775    if sexp.type_of() == SEXPTYPE::INTSXP {
1776        Coerced::<bool, i32>::try_from_sexp(sexp).map(Coerced::into_inner)
1777    } else {
1778        bool::try_from_sexp(sexp)
1779    }
1780}
1781
1782/// Convert a `coerce` boolean vector, retaining indexed, batched diagnostics.
1783#[doc(hidden)]
1784pub fn try_from_sexp_coerced_bool_vec(sexp: SEXP) -> Result<Vec<bool>, SexpError> {
1785    if sexp.type_of() == SEXPTYPE::INTSXP {
1786        let slice: &[i32] = TryFromSexp::try_from_sexp(sexp)?;
1787        coerce_slice_to_vec(slice)
1788    } else {
1789        Vec::<bool>::try_from_sexp(sexp)
1790    }
1791}
1792
1793/// Convert a `coerce` native `i32` parameter.
1794///
1795/// `INTSXP` takes exactly the bare conversion (NA rejected, as always for a
1796/// scalar `i32`). Whole-valued doubles, logicals, and raws go through the
1797/// checked multi-source path the non-native integers use, so `3` reaches an
1798/// `i32` parameter as `3L` while `3.5`, `NA`, and out-of-range values are errors.
1799#[doc(hidden)]
1800pub fn try_from_sexp_coerced_i32(sexp: SEXP) -> Result<i32, SexpError> {
1801    if sexp.type_of() == SEXPTYPE::INTSXP {
1802        i32::try_from_sexp(sexp)
1803    } else {
1804        coerced_scalars::try_from_sexp_numeric_scalar(sexp)
1805    }
1806}
1807
1808/// Convert a `coerce` native `f64` parameter.
1809///
1810/// `REALSXP` takes exactly the bare conversion (`NA_real_` passes through).
1811/// Integers, logicals, and raws widen losslessly, and an integer or logical NA
1812/// becomes `NA_real_` as `as.numeric()` would produce it: the declared type can
1813/// carry NA, so coercion propagates it rather than inventing an error.
1814#[doc(hidden)]
1815pub fn try_from_sexp_coerced_f64(sexp: SEXP) -> Result<f64, SexpError> {
1816    match sexp.type_of() {
1817        SEXPTYPE::INTSXP => {
1818            let v: Option<i32> = TryFromSexp::try_from_sexp(sexp)?;
1819            Ok(v.map_or(NA_REAL, f64::from))
1820        }
1821        SEXPTYPE::LGLSXP => {
1822            let v: RLogical = TryFromSexp::try_from_sexp(sexp)?;
1823            Ok(if v.is_na() {
1824                NA_REAL
1825            } else {
1826                f64::from(v.to_i32())
1827            })
1828        }
1829        SEXPTYPE::RAWSXP => {
1830            let v: u8 = TryFromSexp::try_from_sexp(sexp)?;
1831            Ok(f64::from(v))
1832        }
1833        _ => f64::try_from_sexp(sexp),
1834    }
1835}
1836
1837/// Convert a `coerce` `Vec<i32>` parameter.
1838///
1839/// `INTSXP` takes the bare conversion. Whole-valued doubles, logicals, and raws
1840/// widen element-wise with indexed, batched diagnostics. `NA_real_` and a
1841/// logical NA become `NA_integer_` (the vector can carry NA, unlike the scalar);
1842/// any other NaN, fractional, or out-of-range double is an error.
1843#[doc(hidden)]
1844pub fn try_from_sexp_coerced_i32_vec(sexp: SEXP) -> Result<Vec<i32>, SexpError> {
1845    if sexp.type_of() == SEXPTYPE::INTSXP {
1846        return Vec::<i32>::try_from_sexp(sexp).map_err(SexpError::from);
1847    }
1848    from_numeric_vec_with(
1849        sexp,
1850        Ok,
1851        |v: f64| {
1852            if v.is_na() {
1853                Ok(crate::altrep_traits::NA_INTEGER)
1854            } else {
1855                coerced_scalars::coerce_value(v)
1856            }
1857        },
1858        |v: u8| Ok(i32::from(v)),
1859        // `NA_LOGICAL` and `NA_INTEGER` share the `i32::MIN` sentinel.
1860        |v: RLogical| Ok(v.to_i32()),
1861    )
1862}
1863
1864/// Convert a `coerce` `Vec<f64>` parameter.
1865///
1866/// `REALSXP` takes the bare conversion. Integers, logicals, and raws widen
1867/// element-wise; an integer or logical NA becomes `NA_real_`.
1868#[doc(hidden)]
1869pub fn try_from_sexp_coerced_f64_vec(sexp: SEXP) -> Result<Vec<f64>, SexpError> {
1870    if sexp.type_of() == SEXPTYPE::REALSXP {
1871        return Vec::<f64>::try_from_sexp(sexp).map_err(SexpError::from);
1872    }
1873    from_numeric_vec_with(
1874        sexp,
1875        |v: i32| Ok(if v.is_na() { NA_REAL } else { f64::from(v) }),
1876        Ok,
1877        |v: u8| Ok(f64::from(v)),
1878        |v: RLogical| {
1879            Ok(if v.is_na() {
1880                NA_REAL
1881            } else {
1882                f64::from(v.to_i32())
1883            })
1884        },
1885    )
1886}
1887
1888// region: Direct Vec coercion conversions
1889//
1890// These provide direct `TryFromSexp for Vec<T>` where T is not an R native type
1891// but can be coerced from one. This mirrors the `impl_into_r_via_coerce!` pattern
1892// in into_r.rs for the reverse direction.
1893
1894/// Helper to coerce a slice element-wise into a Vec.
1895///
1896/// Walks the whole slice, accumulating every per-element coercion failure into
1897/// one batched [`SexpError::InvalidValue`] via [`BatchedErrors`]
1898/// (`value out of range (elements 2, 3)`) instead of bailing on the first
1899/// `Err`. The happy path allocates nothing for diagnostics, and a large
1900/// all-failing slice never builds more than [`BATCHED_ERROR_CAP`] messages.
1901#[inline]
1902fn coerce_slice_to_vec<R, T>(slice: &[R]) -> Result<Vec<T>, SexpError>
1903where
1904    R: Copy + TryCoerce<T>,
1905    <R as TryCoerce<T>>::Error: std::fmt::Display,
1906{
1907    let mut result = Vec::with_capacity(slice.len());
1908    let mut errors = BatchedErrors::default();
1909    for (i, v) in slice.iter().copied().enumerate() {
1910        match v.try_coerce() {
1911            Ok(x) => result.push(x),
1912            Err(e) => errors.push(i, || e.to_string()),
1913        }
1914    }
1915    if errors.is_empty() {
1916        Ok(result)
1917    } else {
1918        Err(errors.into_element_error())
1919    }
1920}
1921
1922/// Drive a per-element coercion, batching every failure into one diagnostic.
1923///
1924/// Backs [`from_numeric_vec_with`]'s four SEXP-type branches: successes go into
1925/// the output `Vec`, failures accumulate by reason and position via
1926/// [`BatchedErrors`] (`value out of range (elements 2, 3)`, the grammar of
1927/// `Vec<T>` for a string-parsed type, see [`ParsedRStr`]). Each failure's reason is
1928/// [`SexpError::r_reason`]: an `InvalidValue`'s own text, `NA is not allowed`
1929/// for an NA. The happy path allocates nothing for diagnostics, and a large
1930/// all-failing vector never builds more than [`BATCHED_ERROR_CAP`] messages.
1931#[inline]
1932fn collect_coerced<U>(
1933    len: usize,
1934    iter: impl Iterator<Item = Result<U, SexpError>>,
1935) -> Result<Vec<U>, SexpError> {
1936    let mut result = Vec::with_capacity(len);
1937    let mut errors = BatchedErrors::default();
1938    for (i, item) in iter.enumerate() {
1939        match item {
1940            Ok(v) => result.push(v),
1941            Err(e) => errors.push(i, || e.r_reason(false)),
1942        }
1943    }
1944    if errors.is_empty() {
1945        Ok(result)
1946    } else {
1947        Err(errors.into_element_error())
1948    }
1949}
1950
1951/// Shared SEXP-dispatch shell for coerced numeric/logical/raw vectors.
1952///
1953/// Reads INTSXP/REALSXP/RAWSXP/LGLSXP and applies the per-element map. The only
1954/// behavioural axis (NA policy) lives entirely in the four closures the caller
1955/// passes, so the NA-rejecting [`try_from_sexp_numeric_vec`] and the NA-aware
1956/// `try_from_sexp_numeric_option_vec` (in [`na_vectors`]) share one dispatch.
1957/// LGLSXP NA (`NA_LOGICAL`) and INTSXP NA (`i32::MIN`) round through as raw
1958/// sentinels here; any NA-to-`None` policy is the caller's closure to encode.
1959///
1960/// Per-element coercion failures are accumulated (not short-circuited) and
1961/// reported as one batched diagnostic, by reason and 1-based position (see
1962/// [`collect_coerced`]). Any other SEXPTYPE is a type error naming `numeric`,
1963/// as the `AsNumeric` markers do.
1964#[inline]
1965pub(crate) fn from_numeric_vec_with<U, FI, FD, FR, FL>(
1966    sexp: SEXP,
1967    map_i32: FI,
1968    map_f64: FD,
1969    map_u8: FR,
1970    map_lgl: FL,
1971) -> Result<Vec<U>, SexpError>
1972where
1973    FI: Fn(i32) -> Result<U, SexpError>,
1974    FD: Fn(f64) -> Result<U, SexpError>,
1975    FR: Fn(u8) -> Result<U, SexpError>,
1976    FL: Fn(RLogical) -> Result<U, SexpError>,
1977{
1978    let actual = sexp.type_of();
1979    match actual {
1980        SEXPTYPE::INTSXP => {
1981            let slice: &[i32] = unsafe { sexp.as_slice() };
1982            collect_coerced(slice.len(), slice.iter().copied().map(map_i32))
1983        }
1984        SEXPTYPE::REALSXP => {
1985            let slice: &[f64] = unsafe { sexp.as_slice() };
1986            collect_coerced(slice.len(), slice.iter().copied().map(map_f64))
1987        }
1988        SEXPTYPE::RAWSXP => {
1989            let slice: &[u8] = unsafe { sexp.as_slice() };
1990            collect_coerced(slice.len(), slice.iter().copied().map(map_u8))
1991        }
1992        SEXPTYPE::LGLSXP => {
1993            let slice: &[RLogical] = unsafe { sexp.as_slice() };
1994            collect_coerced(slice.len(), slice.iter().copied().map(map_lgl))
1995        }
1996        _ => Err(SexpTypeError {
1997            expected: SEXPTYPE::REALSXP,
1998            actual,
1999        }
2000        .into()),
2001    }
2002}
2003
2004/// Shared SEXP-dispatch shell for the STRSXP (character vector) walk.
2005///
2006/// String-side counterpart of [`from_numeric_vec_with`]: checks `STRSXP`,
2007/// walks each element via `string_elt`, and applies the per-element `map`
2008/// closure to the raw CHARSXP. NA/blank-string policy (`""` vs `None` vs
2009/// error) and the target representation (`String` vs `&str` vs `Cow`) are
2010/// entirely the caller's closure to encode — this only centralizes the type
2011/// check and the walk.
2012///
2013/// Mirrors `from_numeric_vec_with`'s choice to route both the checked and
2014/// unchecked `TryFromSexp` paths through the same (checked-FFI) walk — none
2015/// of the current STRSXP vector impls define a distinct unchecked fast path
2016/// (they fall back to `TryFromSexp`'s default `try_from_sexp_unchecked`, which
2017/// just calls the checked version), so there is no unchecked-FFI twin here.
2018#[inline]
2019pub(crate) fn map_strsxp_with<U>(
2020    sexp: SEXP,
2021    mut map: impl FnMut(SEXP /* charsxp */, usize) -> Result<U, SexpError>,
2022) -> Result<Vec<U>, SexpError> {
2023    let actual = sexp.type_of();
2024    if actual != SEXPTYPE::STRSXP {
2025        return Err(SexpTypeError {
2026            expected: SEXPTYPE::STRSXP,
2027            actual,
2028        }
2029        .into());
2030    }
2031
2032    let len = sexp.len();
2033    let mut result = Vec::with_capacity(len);
2034    for i in 0..len {
2035        let charsxp = sexp.string_elt(i as crate::R_xlen_t);
2036        result.push(map(charsxp, i)?);
2037    }
2038    Ok(result)
2039}
2040
2041/// Why a value that `Rf_asInteger` / `Rf_asReal` / `Rf_asLogical` (or a raw
2042/// coercion) could not reduce to one non-`NA` scalar was refused, in R terms:
2043/// `got NULL`, `got length 0`, `got "abc"` (a string that does not parse),
2044/// `NA is not allowed`, or `got <type>` for a value of another kind.
2045///
2046/// The reason of a sidecar scalar setter's argument error
2047/// (`'value' must be a number: got "abc"`, #1594). Those setters read the
2048/// value with the `Rf_as*` coercions, which report failure as `NA` with no
2049/// error value, so the reason is worded from the rejected value itself.
2050#[doc(hidden)]
2051pub fn scalar_rejection_reason(value: SEXP) -> String {
2052    let ty = value.type_of();
2053    if ty == SEXPTYPE::NILSXP {
2054        return "got NULL".to_string();
2055    }
2056    let atomic = #[allow(non_exhaustive_omitted_patterns)] match ty {
    SEXPTYPE::LGLSXP | SEXPTYPE::INTSXP | SEXPTYPE::REALSXP |
        SEXPTYPE::CPLXSXP | SEXPTYPE::STRSXP | SEXPTYPE::RAWSXP => true,
    _ => false,
}matches!(
2057        ty,
2058        SEXPTYPE::LGLSXP
2059            | SEXPTYPE::INTSXP
2060            | SEXPTYPE::REALSXP
2061            | SEXPTYPE::CPLXSXP
2062            | SEXPTYPE::STRSXP
2063            | SEXPTYPE::RAWSXP
2064    );
2065    if !atomic {
2066        return ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("got {0}",
                crate::typed_list::sexptype_name(ty)))
    })format!("got {}", crate::typed_list::sexptype_name(ty));
2067    }
2068    if value.len() == 0 {
2069        return "got length 0".to_string();
2070    }
2071    if ty == SEXPTYPE::STRSXP {
2072        let charsxp = value.string_elt(0);
2073        if charsxp != SEXP::na_string() {
2074            // SAFETY: a non-NA CHARSXP of a live STRSXP.
2075            return ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("got {0:?}",
                unsafe { charsxp_to_str(charsxp) }))
    })format!("got {:?}", unsafe { charsxp_to_str(charsxp) });
2076        }
2077    }
2078    "NA is not allowed".to_string()
2079}
2080
2081/// Shared scalar-STRSXP prologue: type-check + `len == 1` + `string_elt(0)`.
2082///
2083/// Returns the raw CHARSXP; NA (`SEXP::na_string()`) and blank-string
2084/// (`SEXP::blank_string()`) policy stay the caller's decision — some sites
2085/// error on NA, some map it to `""`, some to `None`, and `String`'s blank
2086/// handling has historically differed from `&str`'s (see audit D6 finding
2087/// #3), so this helper does not paper over that divergence.
2088#[inline]
2089pub(crate) fn scalar_charsxp(sexp: SEXP) -> Result<SEXP, SexpError> {
2090    let actual = sexp.type_of();
2091    if actual != SEXPTYPE::STRSXP {
2092        return Err(SexpTypeError {
2093            expected: SEXPTYPE::STRSXP,
2094            actual,
2095        }
2096        .into());
2097    }
2098
2099    let len = sexp.len();
2100    if len != 1 {
2101        return Err(SexpLengthError {
2102            expected: 1,
2103            actual: len,
2104        }
2105        .into());
2106    }
2107
2108    Ok(sexp.string_elt(0))
2109}
2110
2111/// Unchecked-FFI variant of [`scalar_charsxp`] — uses `len_unchecked` /
2112/// `string_elt_unchecked`.
2113///
2114/// # Safety
2115///
2116/// Must be called from R's main thread (same contract as
2117/// [`TryFromSexp::try_from_sexp_unchecked`]).
2118#[inline]
2119pub(crate) unsafe fn scalar_charsxp_unchecked(sexp: SEXP) -> Result<SEXP, SexpError> {
2120    let actual = sexp.type_of();
2121    if actual != SEXPTYPE::STRSXP {
2122        return Err(SexpTypeError {
2123            expected: SEXPTYPE::STRSXP,
2124            actual,
2125        }
2126        .into());
2127    }
2128
2129    let len = unsafe { sexp.len_unchecked() };
2130    if len != 1 {
2131        return Err(SexpLengthError {
2132            expected: 1,
2133            actual: len,
2134        }
2135        .into());
2136    }
2137
2138    Ok(unsafe { sexp.string_elt_unchecked(0) })
2139}
2140
2141/// Shared SEXP-dispatch shell for the VECSXP (list) walk.
2142///
2143/// List-side counterpart of [`from_numeric_vec_with`] / [`map_strsxp_with`]:
2144/// checks `VECSXP`, walks each element via `vector_elt`, and applies the
2145/// per-element `map` closure (which receives the element's index and SEXP).
2146/// Per-element policy (recursion, `NILSXP` → `None`, duplicate-pointer
2147/// aliasing checks, …) lives entirely in the closure.
2148#[inline]
2149pub(crate) fn map_vecsxp_with<U>(
2150    sexp: SEXP,
2151    mut map: impl FnMut(usize, SEXP) -> Result<U, SexpError>,
2152) -> Result<Vec<U>, SexpError> {
2153    let actual = sexp.type_of();
2154    if actual != SEXPTYPE::VECSXP {
2155        return Err(SexpTypeError {
2156            expected: SEXPTYPE::VECSXP,
2157            actual,
2158        }
2159        .into());
2160    }
2161
2162    let len = sexp.len();
2163    let mut result = Vec::with_capacity(len);
2164    for i in 0..len {
2165        let elem = sexp.vector_elt(i as crate::R_xlen_t);
2166        result.push(map(i, elem)?);
2167    }
2168    Ok(result)
2169}
2170
2171/// Unchecked-FFI variant of [`map_vecsxp_with`] — uses `len_unchecked` /
2172/// `vector_elt_unchecked` for the type-check and walk.
2173///
2174/// # Safety
2175///
2176/// Must be called from R's main thread (same contract as
2177/// [`TryFromSexp::try_from_sexp_unchecked`]).
2178#[inline]
2179pub(crate) unsafe fn map_vecsxp_with_unchecked<U>(
2180    sexp: SEXP,
2181    mut map: impl FnMut(usize, SEXP) -> Result<U, SexpError>,
2182) -> Result<Vec<U>, SexpError> {
2183    let actual = sexp.type_of();
2184    if actual != SEXPTYPE::VECSXP {
2185        return Err(SexpTypeError {
2186            expected: SEXPTYPE::VECSXP,
2187            actual,
2188        }
2189        .into());
2190    }
2191
2192    let len = unsafe { sexp.len_unchecked() };
2193    let mut result = Vec::with_capacity(len);
2194    for i in 0..len {
2195        let elem = unsafe { sexp.vector_elt_unchecked(i as crate::R_xlen_t) };
2196        result.push(map(i, elem)?);
2197    }
2198    Ok(result)
2199}
2200
2201/// Convert numeric/logical/raw vectors to `Vec<T>` with element-wise coercion.
2202///
2203/// An R `NA` is an error at its index, whichever storage carries it: `NA_integer_`
2204/// and a logical `NA` are caught here (the `i32::MIN` sentinel would otherwise
2205/// coerce to a finite number), and `NA_real_`, which an integer target refuses
2206/// as a NaN, is reported as NA by [`coerce_real_value`]. Bind
2207/// `Vec<Option<T>>` (see [`na_vectors`]) when the caller can pass NA. Per-element
2208/// failures batch into one diagnostic, by reason and 1-based position.
2209#[inline]
2210fn try_from_sexp_numeric_vec<T>(sexp: SEXP) -> Result<Vec<T>, SexpError>
2211where
2212    i32: TryCoerce<T>,
2213    f64: TryCoerce<T>,
2214    u8: TryCoerce<T>,
2215    <i32 as TryCoerce<T>>::Error: std::fmt::Display,
2216    <f64 as TryCoerce<T>>::Error: std::fmt::Display,
2217    <u8 as TryCoerce<T>>::Error: std::fmt::Display,
2218{
2219    from_numeric_vec_with(
2220        sexp,
2221        |v: i32| {
2222            if v.is_na() {
2223                Err(SexpNaError {
2224                    sexp_type: SEXPTYPE::INTSXP,
2225                }
2226                .into())
2227            } else {
2228                coerce_value(v)
2229            }
2230        },
2231        // `NA_real_` fails an integer target's coercion as NaN; say NA, as for
2232        // the other storages. (A float target keeps it, as NaN.)
2233        coerce_real_value,
2234        coerce_value,
2235        |v: RLogical| {
2236            if v.is_na() {
2237                Err(SexpNaError {
2238                    sexp_type: SEXPTYPE::LGLSXP,
2239                }
2240                .into())
2241            } else {
2242                coerce_value(v.to_i32())
2243            }
2244        },
2245    )
2246}
2247
2248/// Implement `TryFromSexp for Vec<$target>` by coercing from integer/real/logical/raw.
2249macro_rules! impl_vec_try_from_sexp_numeric {
2250    ($target:ty) => {
2251        impl TryFromSexp for Vec<$target> {
2252            type Error = SexpError;
2253
2254            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2255                try_from_sexp_numeric_vec(sexp)
2256            }
2257
2258            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2259                try_from_sexp_numeric_vec(sexp)
2260            }
2261        }
2262    };
2263}
2264
2265impl TryFromSexp for Vec<i8> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
}impl_vec_try_from_sexp_numeric!(i8);
2266impl TryFromSexp for Vec<i16> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
}impl_vec_try_from_sexp_numeric!(i16);
2267impl TryFromSexp for Vec<i64> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
}impl_vec_try_from_sexp_numeric!(i64);
2268impl TryFromSexp for Vec<isize> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
}impl_vec_try_from_sexp_numeric!(isize);
2269impl TryFromSexp for Vec<u16> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
}impl_vec_try_from_sexp_numeric!(u16);
2270impl TryFromSexp for Vec<u32> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
}impl_vec_try_from_sexp_numeric!(u32);
2271impl TryFromSexp for Vec<u64> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
}impl_vec_try_from_sexp_numeric!(u64);
2272impl TryFromSexp for Vec<usize> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
}impl_vec_try_from_sexp_numeric!(usize);
2273impl TryFromSexp for Vec<f32> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_vec(sexp)
    }
}impl_vec_try_from_sexp_numeric!(f32);
2274
2275/// Convert R logical vector (LGLSXP) to `Vec<bool>` (errors on NA).
2276impl TryFromSexp for Vec<bool> {
2277    type Error = SexpError;
2278
2279    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2280        let actual = sexp.type_of();
2281        if actual != SEXPTYPE::LGLSXP {
2282            return Err(SexpTypeError {
2283                expected: SEXPTYPE::LGLSXP,
2284                actual,
2285            }
2286            .into());
2287        }
2288        let slice: &[RLogical] = unsafe { sexp.as_slice() };
2289        coerce_slice_to_vec(slice)
2290    }
2291
2292    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2293        Self::try_from_sexp(sexp)
2294    }
2295}
2296// endregion
2297
2298// region: Direct HashSet / BTreeSet coercion conversions
2299
2300/// Convert numeric/logical/raw vectors to a set type with element-wise coercion.
2301///
2302/// Inherits [`try_from_sexp_numeric_vec`]'s batching.
2303#[inline]
2304fn try_from_sexp_numeric_set<T, S>(sexp: SEXP) -> Result<S, SexpError>
2305where
2306    S: std::iter::FromIterator<T>,
2307    i32: TryCoerce<T>,
2308    f64: TryCoerce<T>,
2309    u8: TryCoerce<T>,
2310    <i32 as TryCoerce<T>>::Error: std::fmt::Display,
2311    <f64 as TryCoerce<T>>::Error: std::fmt::Display,
2312    <u8 as TryCoerce<T>>::Error: std::fmt::Display,
2313{
2314    let vec = try_from_sexp_numeric_vec(sexp)?;
2315    Ok(vec.into_iter().collect())
2316}
2317
2318macro_rules! impl_set_try_from_sexp_numeric {
2319    ($set_ty:ident, $target:ty) => {
2320        impl TryFromSexp for $set_ty<$target> {
2321            type Error = SexpError;
2322
2323            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2324                try_from_sexp_numeric_set(sexp)
2325            }
2326
2327            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2328                try_from_sexp_numeric_set(sexp)
2329            }
2330        }
2331    };
2332}
2333
2334impl TryFromSexp for HashSet<i8> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(HashSet, i8);
2335impl TryFromSexp for HashSet<i16> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(HashSet, i16);
2336impl TryFromSexp for HashSet<i64> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(HashSet, i64);
2337impl TryFromSexp for HashSet<isize> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(HashSet, isize);
2338impl TryFromSexp for HashSet<u16> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(HashSet, u16);
2339impl TryFromSexp for HashSet<u32> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(HashSet, u32);
2340impl TryFromSexp for HashSet<u64> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(HashSet, u64);
2341impl TryFromSexp for HashSet<usize> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(HashSet, usize);
2342
2343impl TryFromSexp for BTreeSet<i8> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(BTreeSet, i8);
2344impl TryFromSexp for BTreeSet<i16> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(BTreeSet, i16);
2345impl TryFromSexp for BTreeSet<i64> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(BTreeSet, i64);
2346impl TryFromSexp for BTreeSet<isize> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(BTreeSet, isize);
2347impl TryFromSexp for BTreeSet<u16> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(BTreeSet, u16);
2348impl TryFromSexp for BTreeSet<u32> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(BTreeSet, u32);
2349impl TryFromSexp for BTreeSet<u64> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(BTreeSet, u64);
2350impl TryFromSexp for BTreeSet<usize> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        try_from_sexp_numeric_set(sexp)
    }
}impl_set_try_from_sexp_numeric!(BTreeSet, usize);
2351
2352macro_rules! impl_set_try_from_sexp_bool {
2353    ($set_ty:ident) => {
2354        impl TryFromSexp for $set_ty<bool> {
2355            type Error = SexpError;
2356
2357            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2358                let vec: Vec<bool> = TryFromSexp::try_from_sexp(sexp)?;
2359                Ok(vec.into_iter().collect())
2360            }
2361
2362            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2363                Self::try_from_sexp(sexp)
2364            }
2365        }
2366    };
2367}
2368
2369impl TryFromSexp for HashSet<bool> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let vec: Vec<bool> = TryFromSexp::try_from_sexp(sexp)?;
        Ok(vec.into_iter().collect())
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        Self::try_from_sexp(sexp)
    }
}impl_set_try_from_sexp_bool!(HashSet);
2370impl TryFromSexp for BTreeSet<bool> {
    type Error = SexpError;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let vec: Vec<bool> = TryFromSexp::try_from_sexp(sexp)?;
        Ok(vec.into_iter().collect())
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        Self::try_from_sexp(sexp)
    }
}impl_set_try_from_sexp_bool!(BTreeSet);
2371// endregion
2372
2373// region: ExternalPtr conversions
2374
2375use crate::externalptr::{ExternalPtr, TypeMismatchError, TypedExternal};
2376
2377/// Map a downcast [`TypeMismatchError`] to the [`SexpError`] surfaced from
2378/// `ExternalPtr<T>` argument conversion. Shared by the checked and unchecked
2379/// `TryFromSexp` paths below.
2380fn type_mismatch_to_sexp_error(e: TypeMismatchError) -> SexpError {
2381    match e {
2382        TypeMismatchError::NullPointer => {
2383            SexpError::InvalidValue("external pointer is null".to_string())
2384        }
2385        TypeMismatchError::InvalidTypeId => {
2386            SexpError::InvalidValue("external pointer has no valid type id".to_string())
2387        }
2388        TypeMismatchError::Mismatch { expected, found } => SexpError::InvalidValue(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("type mismatch: expected `{0}`, found `{1}`",
                expected, found))
    })format!(
2389            "type mismatch: expected `{}`, found `{}`",
2390            expected, found
2391        )),
2392        // A pointer restored from a saved session: the classed error, with
2393        // its message as the whole argument error.
2394        TypeMismatchError::Restored { .. } | TypeMismatchError::OtherVersion { .. } => {
2395            let message = e.to_string();
2396            let classes: Vec<String> = e
2397                .restored_classes()
2398                .map(|classes| classes.iter().map(|&class| class.to_owned()).collect())
2399                .unwrap_or_default();
2400            let mut error = crate::condition::RError::new(message.clone())
2401                .class(classes)
2402                .argument_message(message);
2403            if let TypeMismatchError::OtherVersion { saved, current, .. } = e {
2404                error = error
2405                    .data("saved_version", saved)
2406                    .data("current_version", current);
2407            }
2408            SexpError::Condition(error)
2409        }
2410    }
2411}
2412
2413/// Error for an `ExternalPtr<T>` argument that is neither a bare `EXTPTRSXP`
2414/// nor a class handle wrapping one (audit A9 — class-wrapped handles like
2415/// `Foo$new(...)` are unwrapped automatically; this fires only when *no*
2416/// `.ptr`/slot/attribute could be recovered at all).
2417fn not_a_handle_error(actual: SEXPTYPE) -> SexpError {
2418    SexpError::InvalidValue(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("expected an external pointer or a miniextendr class object wrapping one, got {0:?}",
                actual))
    })format!(
2419        "expected an external pointer or a miniextendr class object wrapping one, got {:?}",
2420        actual
2421    ))
2422}
2423
2424/// An `ExternalPtr<T>` from a bare `EXTPTRSXP`, or from a class handle
2425/// carrying one (R6 `private$.ptr`, S4 `ptr` slot, S7 `.ptr` attribute, a
2426/// list or environment with `.ptr`; generated Env/S3 handles are already bare
2427/// `EXTPTRSXP`s). A pointer unwrapped from a class handle is rooted for the
2428/// handle's lifetime: R code can drop the class handle's reference while Rust
2429/// holds the pointer, and the finalizer would then free the value.
2430fn external_ptr_from_sexp<T: TypedExternal>(sexp: SEXP) -> Result<ExternalPtr<T>, SexpError> {
2431    let actual = sexp.type_of();
2432    if actual != SEXPTYPE::EXTPTRSXP {
2433        return match unsafe { crate::externalptr::unwrap_class_handle(sexp) } {
2434            // SAFETY: on R's main thread, like every conversion from R.
2435            Some(inner) => unsafe { ExternalPtr::wrap_sexp_with_error(inner) }
2436                .map(|ptr| unsafe { ptr.rooted() })
2437                .map_err(type_mismatch_to_sexp_error),
2438            None => Err(not_a_handle_error(actual)),
2439        };
2440    }
2441
2442    // Use ExternalPtr's type-checked constructor
2443    unsafe { ExternalPtr::wrap_sexp_with_error(sexp) }.map_err(type_mismatch_to_sexp_error)
2444}
2445
2446/// The handle a generated wrapper borrows for a `&T` / `&mut T` /
2447/// [`ExternalPtrRef`](crate::ExternalPtrRef) /
2448/// [`ExternalPtrMut`](crate::ExternalPtrMut) argument: converted like an
2449/// `ExternalPtr<T>` argument, without its `T: Send` bound, since a borrowed
2450/// argument keeps the call on R's main thread.
2451#[doc(hidden)]
2452#[inline]
2453pub fn __mx_external_arg<T: TypedExternal>(sexp: SEXP) -> Result<ExternalPtr<T>, SexpError> {
2454    external_ptr_from_sexp(sexp)
2455}
2456
2457/// [`__mx_external_arg`] for an `Option` argument: `NULL` is `None`.
2458#[doc(hidden)]
2459#[inline]
2460pub fn __mx_optional_external_arg<T: TypedExternal>(
2461    sexp: SEXP,
2462) -> Result<Option<ExternalPtr<T>>, SexpError> {
2463    if sexp.type_of() == SEXPTYPE::NILSXP {
2464        return Ok(None);
2465    }
2466    external_ptr_from_sexp(sexp).map(Some)
2467}
2468
2469/// Convert R EXTPTRSXP to `ExternalPtr<T>`.
2470///
2471/// This enables using `ExternalPtr<T>` as parameter types in `#[miniextendr]`
2472/// functions, for a body that keeps or consumes the handle; a body that only
2473/// reads the value takes `&T`.
2474///
2475/// # Example
2476///
2477/// ```ignore
2478/// #[derive(ExternalPtr)]
2479/// struct MyData { value: i32 }
2480///
2481/// #[miniextendr]
2482/// fn process(data: ExternalPtr<MyData>) -> i32 {
2483///     data.borrow().value
2484/// }
2485/// ```
2486impl<T: TypedExternal + Send> TryFromSexp for ExternalPtr<T> {
2487    type Error = SexpError;
2488
2489    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2490        external_ptr_from_sexp(sexp)
2491    }
2492
2493    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2494        let actual = sexp.type_of();
2495        if actual != SEXPTYPE::EXTPTRSXP {
2496            return match unsafe { crate::externalptr::unwrap_class_handle(sexp) } {
2497                Some(inner) => unsafe { ExternalPtr::wrap_sexp_unchecked(inner) }
2498                    // SAFETY: on R's main thread (caller contract).
2499                    .map(|ptr| unsafe { ptr.rooted() })
2500                    .ok_or_else(|| {
2501                        SexpError::InvalidValue(
2502                            "failed to convert external pointer: type mismatch or null pointer"
2503                                .to_string(),
2504                        )
2505                    }),
2506                None => Err(not_a_handle_error(actual)),
2507            };
2508        }
2509
2510        // Use ExternalPtr's type-checked constructor (unchecked variant)
2511        unsafe { ExternalPtr::wrap_sexp_unchecked(sexp) }.ok_or_else(|| {
2512            SexpError::InvalidValue(
2513                "failed to convert external pointer: type mismatch or null pointer".to_string(),
2514            )
2515        })
2516    }
2517}
2518
2519impl<T: TypedExternal + Send> TryFromSexp for Option<ExternalPtr<T>> {
2520    type Error = SexpError;
2521
2522    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2523        if sexp.type_of() == SEXPTYPE::NILSXP {
2524            return Ok(None);
2525        }
2526        let ptr: ExternalPtr<T> = TryFromSexp::try_from_sexp(sexp)?;
2527        Ok(Some(ptr))
2528    }
2529
2530    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2531        if sexp.type_of() == SEXPTYPE::NILSXP {
2532            return Ok(None);
2533        }
2534        let ptr: ExternalPtr<T> = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
2535        Ok(Some(ptr))
2536    }
2537}
2538
2539/// Convert an R list (VECSXP) of external pointers to `Vec<ExternalPtr<T>>`.
2540///
2541/// Each element must be an `EXTPTRSXP` carrying a `T`; conversion delegates to
2542/// [`ExternalPtr::<T>::try_from_sexp`] per element. This lets `#[miniextendr]`
2543/// functions accept an R `list()` of opaque handles (issue #827). A user crate
2544/// can't write this impl itself — the orphan rule rejects
2545/// `impl TryFromSexp for Vec<ExternalPtr<T>>` there because both `Vec` and
2546/// `TryFromSexp` are foreign — so it lives here, keyed on `ExternalPtr<T>` to
2547/// avoid colliding with the atomic-vector impls.
2548impl<T: TypedExternal + Send> TryFromSexp for Vec<ExternalPtr<T>> {
2549    type Error = SexpError;
2550
2551    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2552        map_vecsxp_with(sexp, |_i, elem| {
2553            <ExternalPtr<T> as TryFromSexp>::try_from_sexp(elem)
2554        })
2555    }
2556
2557    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2558        unsafe {
2559            map_vecsxp_with_unchecked(sexp, |_i, elem| {
2560                <ExternalPtr<T> as TryFromSexp>::try_from_sexp_unchecked(elem)
2561            })
2562        }
2563    }
2564}
2565
2566/// Convert an R list (VECSXP) of external pointers / `NULL`s to
2567/// `Vec<Option<ExternalPtr<T>>>`. `NULL` elements map to `None`; every other
2568/// element must be an `EXTPTRSXP` carrying a `T` (issue #827).
2569impl<T: TypedExternal + Send> TryFromSexp for Vec<Option<ExternalPtr<T>>> {
2570    type Error = SexpError;
2571
2572    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2573        map_vecsxp_with(sexp, |_i, elem| {
2574            <Option<ExternalPtr<T>> as TryFromSexp>::try_from_sexp(elem)
2575        })
2576    }
2577
2578    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2579        unsafe {
2580            map_vecsxp_with_unchecked(sexp, |_i, elem| {
2581                <Option<ExternalPtr<T>> as TryFromSexp>::try_from_sexp_unchecked(elem)
2582            })
2583        }
2584    }
2585}
2586// endregion
2587
2588// region: R connections — TryFromSexp impls (issue #175, #176)
2589
2590#[cfg(feature = "connections")]
2591mod connections_from_r {
2592    use crate::SEXP;
2593    use crate::connection::access::{self, ConnState};
2594    use crate::connection::{RNullConnection, RStderr, RStdin, RStdout};
2595    use crate::from_r::{SexpError, TryFromSexp};
2596
2597    // Strict validation: confirm description == expected_desc and class == "terminal".
2598    unsafe fn validate_terminal(sexp: SEXP, expected_desc: &str) -> Result<(), SexpError> {
2599        let ConnState {
2600            description: desc,
2601            class: cls,
2602            ..
2603        } = unsafe { access::state(sexp) };
2604        if desc != expected_desc {
2605            return Err(SexpError::InvalidValue(format!(
2606                "expected terminal connection with description {:?}, got {:?}",
2607                expected_desc, desc
2608            )));
2609        }
2610        if cls != "terminal" {
2611            return Err(SexpError::InvalidValue(format!(
2612                "expected class \"terminal\", got {:?}",
2613                cls
2614            )));
2615        }
2616        Ok(())
2617    }
2618
2619    impl TryFromSexp for RStdin {
2620        type Error = SexpError;
2621
2622        fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2623            unsafe { validate_terminal(sexp, "stdin") }?;
2624            Ok(RStdin)
2625        }
2626
2627        unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2628            Self::try_from_sexp(sexp)
2629        }
2630    }
2631
2632    impl TryFromSexp for RStdout {
2633        type Error = SexpError;
2634
2635        fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2636            unsafe { validate_terminal(sexp, "stdout") }?;
2637            Ok(RStdout)
2638        }
2639
2640        unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2641            Self::try_from_sexp(sexp)
2642        }
2643    }
2644
2645    impl TryFromSexp for RStderr {
2646        type Error = SexpError;
2647
2648        fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2649            unsafe { validate_terminal(sexp, "stderr") }?;
2650            Ok(RStderr)
2651        }
2652
2653        unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2654            Self::try_from_sexp(sexp)
2655        }
2656    }
2657
2658    /// Accepts any open, write-capable connection — not just the null device.
2659    ///
2660    /// This is intentional: validating against `description == "/dev/null"` /
2661    /// `"NUL"` is brittle across platforms, and the type is a write target, not
2662    /// a specific device. Substituting a `file()` connection for
2663    /// `RNullConnection` is supported. The handle borrows the connection: the
2664    /// R caller owns it, and dropping the handle leaves it open.
2665    impl TryFromSexp for RNullConnection {
2666        type Error = SexpError;
2667
2668        fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2669            let state = unsafe { access::state(sexp) };
2670            if !state.is_open {
2671                return Err(SexpError::InvalidValue(
2672                    "expected an open connection".to_string(),
2673                ));
2674            }
2675            if !state.can_write {
2676                return Err(SexpError::InvalidValue(
2677                    "expected a write-capable connection".to_string(),
2678                ));
2679            }
2680            // Preserve the SEXP so it lives as long as this Rust struct.
2681            unsafe { crate::sys::R_PreserveObject(sexp) };
2682            Ok(unsafe { RNullConnection::from_preserved_sexp(sexp) })
2683        }
2684
2685        unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2686            Self::try_from_sexp(sexp)
2687        }
2688    }
2689}
2690
2691// endregion
2692
2693// region: txtProgressBar — TryFromSexp (issue #177)
2694
2695#[cfg(feature = "connections")]
2696mod txt_progress_bar_from_r {
2697    use crate::from_r::{SexpError, TryFromSexp};
2698    use crate::sys::R_PreserveObject;
2699    use crate::txt_progress_bar::RTxtProgressBar;
2700    use crate::{SEXP, SexpExt};
2701
2702    /// The handle borrows the bar: the R caller owns it, and dropping the
2703    /// handle leaves it open.
2704    impl TryFromSexp for RTxtProgressBar {
2705        type Error = SexpError;
2706
2707        fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2708            // Must be a list (VECSXP) with class "txtProgressBar".
2709            if !sexp.inherits_class(c"txtProgressBar") {
2710                return Err(SexpError::InvalidValue(
2711                    "expected a SEXP with class \"txtProgressBar\"".to_string(),
2712                ));
2713            }
2714            // Pin on the precious list so GC cannot collect while Rust holds it.
2715            unsafe { R_PreserveObject(sexp) };
2716            Ok(unsafe { RTxtProgressBar::from_preserved_sexp(sexp) })
2717        }
2718
2719        unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2720            Self::try_from_sexp(sexp)
2721        }
2722    }
2723}
2724
2725// endregion
2726
2727// region: Helper macros for feature-gated modules
2728
2729/// Implement `TryFromSexp for Option<T>` where T already implements TryFromSexp.
2730///
2731/// NULL → None, otherwise delegates to T::try_from_sexp and wraps in Some.
2732///
2733/// Crate-internal: the orphan rule forbids `impl TryFromSexp for Option<T>`
2734/// outside `miniextendr-api` (#1731), so this only expands here.
2735macro_rules! impl_option_try_from_sexp {
2736    ($t:ty) => {
2737        impl $crate::from_r::TryFromSexp for Option<$t> {
2738            const NATIVE_BORROW: Option<$crate::from_r::NativeBorrow> =
2739                <$t as $crate::from_r::TryFromSexp>::NATIVE_BORROW;
2740            const CHARACTER_ONLY: bool = <$t as $crate::from_r::TryFromSexp>::CHARACTER_ONLY;
2741
2742            type Error = $crate::from_r::SexpError;
2743            // `NULL` (`None`) is "not given" and passes `no_na`.
2744            #[inline]
2745            fn __mx_has_na(&self) -> bool {
2746                self.as_ref()
2747                    .is_some_and(<$t as $crate::from_r::TryFromSexp>::__mx_has_na)
2748            }
2749
2750            fn try_from_sexp(sexp: $crate::SEXP) -> Result<Self, Self::Error> {
2751                use $crate::{SEXPTYPE, SexpExt};
2752                if sexp.type_of() == SEXPTYPE::NILSXP {
2753                    return Ok(None);
2754                }
2755                <$t as $crate::from_r::TryFromSexp>::try_from_sexp(sexp).map(Some)
2756            }
2757
2758            unsafe fn try_from_sexp_unchecked(sexp: $crate::SEXP) -> Result<Self, Self::Error> {
2759                use $crate::{SEXPTYPE, SexpExt};
2760                if sexp.type_of() == SEXPTYPE::NILSXP {
2761                    return Ok(None);
2762                }
2763                unsafe {
2764                    <$t as $crate::from_r::TryFromSexp>::try_from_sexp_unchecked(sexp).map(Some)
2765                }
2766            }
2767        }
2768    };
2769}
2770pub(crate) use impl_option_try_from_sexp;
2771
2772/// Implement `TryFromSexp for Vec<T>` from R list (VECSXP).
2773///
2774/// Each element is converted via T::try_from_sexp. Crate-internal, like
2775/// [`impl_option_try_from_sexp!`]: `Vec<T>` impls are orphans downstream.
2776#[allow(unused_macros)] // every caller is behind a feature
2777macro_rules! impl_vec_try_from_sexp_list {
2778    ($t:ty) => {
2779        impl $crate::from_r::TryFromSexp for Vec<$t> {
2780            const NATIVE_BORROW: Option<$crate::from_r::NativeBorrow> =
2781                $crate::from_r::NativeBorrow::in_list(
2782                    <$t as $crate::from_r::TryFromSexp>::NATIVE_BORROW,
2783                );
2784
2785            type Error = $crate::from_r::SexpError;
2786
2787            fn try_from_sexp(sexp: $crate::SEXP) -> Result<Self, Self::Error> {
2788                use $crate::from_r::SexpTypeError;
2789                use $crate::{SEXPTYPE, SexpExt};
2790
2791                let actual = sexp.type_of();
2792                if actual != SEXPTYPE::VECSXP {
2793                    return Err(SexpTypeError {
2794                        expected: SEXPTYPE::VECSXP,
2795                        actual,
2796                    }
2797                    .into());
2798                }
2799
2800                let len = sexp.len();
2801                let mut result = Vec::with_capacity(len);
2802                for i in 0..len {
2803                    let elem = sexp.vector_elt(i as $crate::R_xlen_t);
2804                    result.push(<$t as $crate::from_r::TryFromSexp>::try_from_sexp(elem)?);
2805                }
2806                Ok(result)
2807            }
2808
2809            unsafe fn try_from_sexp_unchecked(sexp: $crate::SEXP) -> Result<Self, Self::Error> {
2810                use $crate::from_r::SexpTypeError;
2811                use $crate::{SEXPTYPE, SexpExt};
2812
2813                let actual = sexp.type_of();
2814                if actual != SEXPTYPE::VECSXP {
2815                    return Err(SexpTypeError {
2816                        expected: SEXPTYPE::VECSXP,
2817                        actual,
2818                    }
2819                    .into());
2820                }
2821
2822                let len = unsafe { sexp.len_unchecked() };
2823                let mut result = Vec::with_capacity(len);
2824                for i in 0..len {
2825                    let elem = unsafe { sexp.vector_elt_unchecked(i as $crate::R_xlen_t) };
2826                    result.push(unsafe {
2827                        <$t as $crate::from_r::TryFromSexp>::try_from_sexp_unchecked(elem)?
2828                    });
2829                }
2830                Ok(result)
2831            }
2832        }
2833    };
2834}
2835// Every caller is a feature-gated integration (serde, aho-corasick, globset).
2836#[allow(unused_imports)]
2837pub(crate) use impl_vec_try_from_sexp_list;
2838
2839/// Implement `TryFromSexp for Vec<Option<T>>` from R list (VECSXP).
2840///
2841/// NULL elements become None, others are converted via T::try_from_sexp.
2842/// Crate-internal, like [`impl_option_try_from_sexp!`].
2843#[allow(unused_macros)] // every caller is behind a feature
2844macro_rules! impl_vec_option_try_from_sexp_list {
2845    ($t:ty) => {
2846        impl $crate::from_r::TryFromSexp for Vec<Option<$t>> {
2847            const NATIVE_BORROW: Option<$crate::from_r::NativeBorrow> =
2848                $crate::from_r::NativeBorrow::in_list(
2849                    <$t as $crate::from_r::TryFromSexp>::NATIVE_BORROW,
2850                );
2851
2852            type Error = $crate::from_r::SexpError;
2853
2854            fn try_from_sexp(sexp: $crate::SEXP) -> Result<Self, Self::Error> {
2855                use $crate::from_r::SexpTypeError;
2856                use $crate::{SEXPTYPE, SexpExt};
2857
2858                let actual = sexp.type_of();
2859                if actual != SEXPTYPE::VECSXP {
2860                    return Err(SexpTypeError {
2861                        expected: SEXPTYPE::VECSXP,
2862                        actual,
2863                    }
2864                    .into());
2865                }
2866
2867                let len = sexp.len();
2868                let mut result = Vec::with_capacity(len);
2869                for i in 0..len {
2870                    let elem = sexp.vector_elt(i as $crate::R_xlen_t);
2871                    if elem == $crate::SEXP::nil() {
2872                        result.push(None);
2873                    } else {
2874                        result.push(Some(<$t as $crate::from_r::TryFromSexp>::try_from_sexp(
2875                            elem,
2876                        )?));
2877                    }
2878                }
2879                Ok(result)
2880            }
2881
2882            unsafe fn try_from_sexp_unchecked(sexp: $crate::SEXP) -> Result<Self, Self::Error> {
2883                use $crate::from_r::SexpTypeError;
2884                use $crate::{SEXPTYPE, SexpExt};
2885
2886                let actual = sexp.type_of();
2887                if actual != SEXPTYPE::VECSXP {
2888                    return Err(SexpTypeError {
2889                        expected: SEXPTYPE::VECSXP,
2890                        actual,
2891                    }
2892                    .into());
2893                }
2894
2895                let len = unsafe { sexp.len_unchecked() };
2896                let mut result = Vec::with_capacity(len);
2897                for i in 0..len {
2898                    let elem = unsafe { sexp.vector_elt_unchecked(i as $crate::R_xlen_t) };
2899                    if elem == $crate::SEXP::nil() {
2900                        result.push(None);
2901                    } else {
2902                        result.push(Some(unsafe {
2903                            <$t as $crate::from_r::TryFromSexp>::try_from_sexp_unchecked(elem)?
2904                        }));
2905                    }
2906                }
2907                Ok(result)
2908            }
2909        }
2910    };
2911}
2912// Every caller is a feature-gated integration (serde, aho-corasick, globset).
2913#[allow(unused_imports)]
2914pub(crate) use impl_vec_option_try_from_sexp_list;
2915
2916/// Cap on the number of per-element failures listed in a batched vector
2917/// conversion error; the remainder is summarized as `"and N more"`.
2918///
2919/// `pub(crate)` (rather than file-private) so [`crate::into_r_as`]'s
2920/// `StorageCoerceError::Batched` (#1097) shares the exact same cap instead of
2921/// drifting from the inbound [`BatchedErrors`] path.
2922pub(crate) const BATCHED_ERROR_CAP: usize = 10;
2923
2924/// Bounded accumulator that folds per-element conversion failures into one
2925/// batched [`SexpError::InvalidValue`], with the elements numbered as R
2926/// counts them (1-based).
2927///
2928/// Backs every vector conversion that walks its input: `Vec<T>` /
2929/// `Vec<Option<T>>` for a string-parsed type ([`ParsedRStr`], #1143), the
2930/// numeric-coercion vector shells [`from_numeric_vec_with`] /
2931/// [`collect_coerced`] / [`coerce_slice_to_vec`] (#1192), tuples, factors,
2932/// the `As*Vec` markers and the optional integrations. Instead of bailing on
2933/// the first NA or failing element, they walk the whole vector and record
2934/// each failure here with its 0-based index; the message says where each one
2935/// is, `(element 2)`.
2936///
2937/// Only the first [`BATCHED_ERROR_CAP`] failures are retained; every later
2938/// failure is counted but its closure is never invoked. This keeps an
2939/// all-failing N-element vector from materialising N `String`s just to
2940/// discard all but 10: the memory held is bounded regardless of input size.
2941/// The remainder is summarized as `"; and N more"`.
2942///
2943/// Two foldings:
2944/// - [`into_element_error`](Self::into_element_error): each entry is the
2945///   failure's reason (`NA is not allowed`, `value out of range`); entries
2946///   with the same reason share one position list:
2947///   `NA is not allowed (elements 2, 4); value out of range (element 3)`.
2948/// - [`into_value_error`](Self::into_value_error): each entry is the
2949///   offending value, listed with the positions after them, as
2950///   [`AsNumericVec`](crate::convert::AsNumericVec) does:
2951///   `non-numeric value(s): "n/a", "<0.1" (elements 2, 5)`.
2952///
2953/// Neither names the Rust type: an argument's conversion condition carries it
2954/// as `e$rust_type` (#1591).
2955#[derive(#[automatically_derived]
impl ::core::default::Default for BatchedErrors {
    #[inline]
    fn default() -> Self {
        BatchedErrors {
            listed: ::core::default::Default::default(),
            total: ::core::default::Default::default(),
        }
    }
}Default)]
2956pub(crate) struct BatchedErrors {
2957    /// `(0-based index, reason or value)` of the first [`BATCHED_ERROR_CAP`]
2958    /// failures.
2959    listed: Vec<(usize, String)>,
2960    total: usize,
2961}
2962
2963impl BatchedErrors {
2964    /// Record the failure of the element at 0-based `index`. `item` (its
2965    /// reason or value) is evaluated, with whatever it allocates, only for the
2966    /// first [`BATCHED_ERROR_CAP`] failures; later ones are counted for the
2967    /// `"and N more"` tail but never built.
2968    #[inline]
2969    pub fn push(&mut self, index: usize, item: impl FnOnce() -> String) {
2970        if self.listed.len() < BATCHED_ERROR_CAP {
2971            self.listed.push((index, item()));
2972        }
2973        self.total += 1;
2974    }
2975
2976    /// Whether any failure has been recorded.
2977    #[inline]
2978    pub fn is_empty(&self) -> bool {
2979        self.total == 0
2980    }
2981
2982    /// Append the `"; and N more"` tail when failures past the cap were counted.
2983    fn write_more_tail(&self, msg: &mut String) {
2984        if self.total > self.listed.len() {
2985            use std::fmt::Write;
2986            let _ = msg.write_fmt(format_args!("; and {0} more", self.total - self.listed.len()))write!(msg, "; and {} more", self.total - self.listed.len());
2987        }
2988    }
2989
2990    /// Fold the recorded `(index, reason)` failures into one
2991    /// [`SexpError::InvalidValue`]; see [`element_message`](Self::element_message).
2992    pub fn into_element_error(self) -> SexpError {
2993        SexpError::InvalidValue(self.element_message())
2994    }
2995
2996    /// The message behind [`into_element_error`](Self::into_element_error):
2997    /// each distinct reason once, in the order it first occurred, followed by
2998    /// the 1-based positions of the elements that failed with it:
2999    ///
3000    /// `NA is not allowed (elements 2, 4); value out of range (element 3)`
3001    pub fn element_message(&self) -> String {
3002        if true {
    if !(self.total > 0) {
        {
            ::core::panicking::panic_fmt(format_args!("batching zero conversion errors"));
        }
    };
};debug_assert!(self.total > 0, "batching zero conversion errors");
3003        let mut groups: Vec<(&str, Vec<usize>)> = Vec::new();
3004        for (index, reason) in &self.listed {
3005            match groups.iter_mut().find(|(r, _)| *r == reason.as_str()) {
3006                Some((_, indices)) => indices.push(*index),
3007                None => groups.push((reason.as_str(), ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [*index]))vec![*index])),
3008            }
3009        }
3010        let mut msg = String::new();
3011        for (k, (reason, indices)) in groups.iter().enumerate() {
3012            if k > 0 {
3013                msg.push_str("; ");
3014            }
3015            msg.push_str(reason);
3016            push_element_positions(&mut msg, indices.iter().copied());
3017        }
3018        self.write_more_tail(&mut msg);
3019        msg
3020    }
3021
3022    /// Fold recorded `(0-based index, offending value)` pairs into one
3023    /// [`SexpError::InvalidValue`] that shows the values and where they are:
3024    ///
3025    /// `<what> value(s): "n/a", "<0.1" (elements 2, 5)`
3026    ///
3027    /// Values are quoted (Rust `Debug` escaping, so invisible characters show),
3028    /// and element numbers are 1-based, as R counts them. Past the cap, the
3029    /// `"; and N more"` tail matches [`into_element_error`](Self::into_element_error).
3030    pub fn into_value_error(self, what: &str) -> SexpError {
3031        SexpError::InvalidValue(self.value_message(what))
3032    }
3033
3034    /// The message behind [`into_value_error`](Self::into_value_error).
3035    fn value_message(&self, what: &str) -> String {
3036        use std::fmt::Write;
3037        if true {
    if !(self.total > 0) {
        {
            ::core::panicking::panic_fmt(format_args!("batching zero rejected values"));
        }
    };
};debug_assert!(self.total > 0, "batching zero rejected values");
3038        let mut msg = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} value(s): ", what))
    })format!("{what} value(s): ");
3039        for (k, (_, value)) in self.listed.iter().enumerate() {
3040            if k > 0 {
3041                msg.push_str(", ");
3042            }
3043            let _ = msg.write_fmt(format_args!("{0:?}", value))write!(msg, "{value:?}");
3044        }
3045        push_element_positions(&mut msg, self.listed.iter().map(|(index, _)| *index));
3046        self.write_more_tail(&mut msg);
3047        msg
3048    }
3049}
3050
3051/// Append ` (element 2)` / ` (elements 2, 5)` for the given 0-based indices,
3052/// numbered as R counts them. The one spelling of an element position in a
3053/// conversion message.
3054pub(crate) fn push_element_positions(
3055    msg: &mut String,
3056    indices: impl ExactSizeIterator<Item = usize>,
3057) {
3058    use std::fmt::Write;
3059    msg.push_str(if indices.len() == 1 {
3060        " (element "
3061    } else {
3062        " (elements "
3063    });
3064    for (k, index) in indices.enumerate() {
3065        if k > 0 {
3066            msg.push_str(", ");
3067        }
3068        let _ = msg.write_fmt(format_args!("{0}", index + 1))write!(msg, "{}", index + 1);
3069    }
3070    msg.push(')');
3071}
3072
3073/// The failures of a list read element by element ([`map_vecsxp_batched`]),
3074/// folded into one error that keeps the classes and fields of the check
3075/// refusals among them.
3076///
3077/// Each failure's reason goes into a [`BatchedErrors`], so the message has the
3078/// grammar of every batched conversion: `got a data frame (element 2); expected
3079/// list, got numeric (elements 3, 5)`. A check's refusal
3080/// ([`SexpError::Condition`]) is worded by its
3081/// [`argument_message`](crate::condition::RError::argument_message) when it has
3082/// one (the type's own words for a value refused as an argument), else by its
3083/// message; any other error by [`SexpError::r_reason`].
3084///
3085/// With no refusal among the failures the error is the batch's
3086/// [`SexpError::InvalidValue`]. With one, it is a [`SexpError::Condition`]
3087/// with the batch's message, the classes of every refusal (in the order they
3088/// first occur) and their fields (the first of each name), so a handler for a
3089/// check's class catches a list of the type as it catches one value of it.
3090#[derive(#[automatically_derived]
impl ::core::default::Default for ElementErrors {
    #[inline]
    fn default() -> Self {
        ElementErrors {
            batch: ::core::default::Default::default(),
            refused: ::core::default::Default::default(),
            class: ::core::default::Default::default(),
            data: ::core::default::Default::default(),
        }
    }
}Default)]
3091pub(crate) struct ElementErrors {
3092    batch: BatchedErrors,
3093    refused: bool,
3094    class: Vec<String>,
3095    data: crate::condition::ConditionData,
3096}
3097
3098impl ElementErrors {
3099    /// Record the failure of the element at 0-based `index`.
3100    pub(crate) fn push(&mut self, index: usize, error: SexpError) {
3101        for refusal in error.reported_conditions() {
3102            self.refused = true;
3103            for class in refusal.classes() {
3104                if !self.class.contains(class) {
3105                    self.class.push(class.clone());
3106                }
3107            }
3108            for (name, value) in refusal.fields() {
3109                if !self.data.iter().any(|(seen, _)| seen == name) {
3110                    self.data.push((name.clone(), value.clone()));
3111                }
3112            }
3113        }
3114        self.batch.push(index, || match &error {
3115            SexpError::Condition(refusal) => refusal
3116                .argument_message_str()
3117                .unwrap_or(refusal.message_str())
3118                .to_string(),
3119            other => other.r_reason(false),
3120        });
3121    }
3122
3123    /// The values read, or the one error for every failure recorded.
3124    pub(crate) fn into_result<T>(self, values: T) -> Result<T, SexpError> {
3125        if self.batch.is_empty() {
3126            return Ok(values);
3127        }
3128        if !self.refused {
3129            return Err(self.batch.into_element_error());
3130        }
3131        let mut error =
3132            crate::condition::RError::new(self.batch.element_message()).class(self.class);
3133        for (name, value) in self.data {
3134            error = error.data(name, value);
3135        }
3136        Err(SexpError::Condition(error))
3137    }
3138}
3139
3140/// Read a list (`VECSXP`) element by element with `read`, which gets each
3141/// element's `SEXP`, and report every element that fails in one error
3142/// ([`ElementErrors`]), its position numbered as R counts it. Anything but a
3143/// `VECSXP` is a type error, and no element is read.
3144pub(crate) fn map_vecsxp_batched<U>(
3145    sexp: SEXP,
3146    mut read: impl FnMut(SEXP) -> Result<U, SexpError>,
3147) -> Result<Vec<U>, SexpError> {
3148    let actual = sexp.type_of();
3149    if actual != SEXPTYPE::VECSXP {
3150        return Err(SexpTypeError {
3151            expected: SEXPTYPE::VECSXP,
3152            actual,
3153        }
3154        .into());
3155    }
3156    let len = sexp.len();
3157    let mut values = Vec::with_capacity(len);
3158    let mut errors = ElementErrors::default();
3159    for i in 0..len {
3160        let index = crate::R_xlen_t::try_from(i).expect("a list index fits R_xlen_t");
3161        match read(sexp.vector_elt(index)) {
3162            Ok(value) => values.push(value),
3163            Err(e) => errors.push(i, e),
3164        }
3165    }
3166    errors.into_result(values)
3167}
3168
3169/// ` (element <k>)` for one 0-based `index`: the position suffix of a single
3170/// per-element failure, as [`BatchedErrors`] writes it.
3171pub(crate) fn element_position(index: usize) -> String {
3172    let mut s = String::new();
3173    push_element_positions(&mut s, std::iter::once(index));
3174    s
3175}
3176
3177// endregion
3178
3179#[cfg(test)]
3180mod tests {
3181    use super::*;
3182
3183    #[test]
3184    fn real_na_follows_r_is_na() {
3185        assert!(NA_REAL.is_na());
3186        // `NA_real_ * 1` in R: the same payload with the quiet bit set.
3187        assert!(f64::from_bits(0x7FF8_0000_0000_07A2).is_na());
3188        // `-NA_real_`: the sign bit flips, R still reads NA.
3189        assert!(f64::from_bits(0xFFF0_0000_0000_07A2).is_na());
3190        assert!(!f64::NAN.is_na());
3191        assert!(!1954.0f64.is_na());
3192        assert!(!f64::from_bits(0x0000_0000_0000_07A2).is_na());
3193    }
3194
3195    #[test]
3196    fn batched_value_error_lists_values_and_one_based_elements() {
3197        let mut errors = BatchedErrors::default();
3198        errors.push(1, || "n/a".to_string());
3199        errors.push(4, || "<0.1".to_string());
3200        assert_eq!(
3201            errors.value_message("non-numeric"),
3202            r#"non-numeric value(s): "n/a", "<0.1" (elements 2, 5)"#
3203        );
3204
3205        let mut one = BatchedErrors::default();
3206        one.push(0, || "a\"b\u{a0}".to_string());
3207        assert_eq!(
3208            one.value_message("non-numeric"),
3209            r#"non-numeric value(s): "a\"b\u{a0}" (element 1)"#
3210        );
3211    }
3212
3213    #[test]
3214    fn batched_value_error_caps_the_listing() {
3215        let mut errors = BatchedErrors::default();
3216        for i in 0..12 {
3217            errors.push(i, || format!("x{i}"));
3218        }
3219        let msg = errors.value_message("non-numeric");
3220        assert!(
3221            msg.starts_with(r#"non-numeric value(s): "x0", "x1","#),
3222            "{msg}"
3223        );
3224        assert!(
3225            msg.ends_with(r#""x9" (elements 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); and 2 more"#),
3226            "{msg}"
3227        );
3228        assert!(!msg.contains("x10"), "{msg}");
3229    }
3230
3231    #[test]
3232    fn batched_element_error_groups_reasons_with_one_based_positions() {
3233        let mut errors = BatchedErrors::default();
3234        errors.push(1, || "NA is not allowed".to_string());
3235        errors.push(2, || "value out of range".to_string());
3236        errors.push(4, || "NA is not allowed".to_string());
3237        assert_eq!(
3238            errors.element_message(),
3239            "NA is not allowed (elements 2, 5); value out of range (element 3)"
3240        );
3241
3242        let mut one = BatchedErrors::default();
3243        one.push(0, || "precision loss".to_string());
3244        let SexpError::InvalidValue(msg) = one.into_element_error() else {
3245            unreachable!()
3246        };
3247        assert_eq!(msg, "precision loss (element 1)");
3248    }
3249
3250    #[test]
3251    fn batched_element_error_caps_the_listing() {
3252        let mut errors = BatchedErrors::default();
3253        for i in 0..13 {
3254            errors.push(i, || "NA is not allowed".to_string());
3255        }
3256        assert_eq!(
3257            errors.element_message(),
3258            "NA is not allowed (elements 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); and 3 more"
3259        );
3260    }
3261
3262    /// `try_from_sexp_via_str_parse!` spells no associated item as
3263    /// `Self::<Name>`, which is ambiguous for an enum with a variant of that
3264    /// name (#1730): `Error` (`TryFromSexp`), `LABEL` (`ParseRStr`) and
3265    /// `Inner` (`TryFromSexpElement`). Compiling this module is the test; the
3266    /// downstream form is `miniextendr-macros/tests/ui/pass/assoc_item_variant_names.rs`.
3267    mod str_parse_enum_with_assoc_item_variants {
3268        #[derive(Debug)]
3269        #[allow(clippy::upper_case_acronyms)]
3270        pub(super) enum Level {
3271            Error,
3272            Err,
3273            LABEL,
3274            Inner,
3275            Value,
3276        }
3277
3278        impl std::str::FromStr for Level {
3279            type Err = String;
3280
3281            fn from_str(s: &str) -> Result<Self, String> {
3282                match s {
3283                    "error" => Ok(Level::Error),
3284                    "err" => Ok(Level::Err),
3285                    "label" => Ok(Level::LABEL),
3286                    "inner" => Ok(Level::Inner),
3287                    "value" => Ok(Level::Value),
3288                    other => Err(format!("unknown level {other:?}")),
3289                }
3290            }
3291        }
3292
3293        crate::try_from_sexp_via_str_parse!(Level, "level", |s| s.parse::<Level>());
3294
3295        /// The container shapes come from the `TryFromSexpElement` blankets,
3296        /// and every shape reads only character input.
3297        #[test]
3298        fn containers_convert() {
3299            use crate::from_r::TryFromSexp;
3300            const {
3301                assert!(<Level as TryFromSexp>::CHARACTER_ONLY);
3302                assert!(<Option<Level> as TryFromSexp>::CHARACTER_ONLY);
3303                assert!(<Vec<Level> as TryFromSexp>::CHARACTER_ONLY);
3304                assert!(<Vec<Option<Level>> as TryFromSexp>::CHARACTER_ONLY);
3305            }
3306        }
3307    }
3308}