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
5152use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
5354use crate::altrep_traits::NA_REAL;
55use crate::coerce::TryCoerce;
56use crate::{IsNA, RLogical, SEXP, SEXPTYPE, SexpExt};
5758/// 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 {
75if !sexp.is_vector() {
76return false;
77 }
78let n = sexp.xlength();
79if sexp.is_data_frame() {
80return (0..n).any(|i| any_na(sexp.vector_elt(i)));
81 }
82let 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.
85match sexp.type_of() {
86 SEXPTYPE::LGLSXPif altrep => {
87 (0..n).any(|i| RLogical::from_i32(sexp.logical_elt(i)).is_na())
88 }
89 SEXPTYPE::INTSXPif altrep => (0..n).any(|i| sexp.integer_elt(i).is_na()),
90 SEXPTYPE::REALSXPif altrep => (0..n).any(|i| sexp.real_elt(i).is_nan()),
91 SEXPTYPE::CPLXSXPif altrep => (0..n).any(|i| {
92let v = sexp.complex_elt(i);
93v.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| {
105let elt = sexp.vector_elt(i);
106elt.is_vector_atomic() && elt.len() == 1 && any_na(elt)
107 }),
108_ => false,
109 }
110}
111112// region: CHARSXP to string conversion
113114/// 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 {
126unsafe { charsxp_to_str_impl(charsxp.r_char(), charsxp) }
127}
128129/// 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 {
132unsafe { charsxp_to_str_impl(charsxp.r_char_unchecked(), charsxp) }
133}
134135/// 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 {
140unsafe {
141let len: usize = charsxp.len();
142let 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.
145if 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}
152153/// `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}
159160/// 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> {
177if charsxp.is_null_or_nil() || charsxp.is_na_string() {
178return None;
179 }
180let ptr = charsxp.r_char();
181if ptr.is_null() {
182return None;
183 }
184Some(
185unsafe { std::ffi::CStr::from_ptr(ptr) }
186 .to_string_lossy()
187 .into_owned(),
188 )
189}
190191/// 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] {
205if len == 0 {
206&[]
207 } else {
208unsafe { std::slice::from_raw_parts(ptr, len) }
209 }
210}
211212/// 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] {
219if len == 0 {
220&mut []
221 } else {
222unsafe { std::slice::from_raw_parts_mut(ptr, len) }
223 }
224}
225226#[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.
230pub expected: SEXPTYPE,
231/// Actual R type encountered.
232pub actual: SEXPTYPE,
233}
234235impl std::fmt::Displayfor SexpTypeError {
236fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237f.write_fmt(format_args!("type mismatch: expected {0:?}, got {1:?}",
self.expected, self.actual))write!(
238f,
239"type mismatch: expected {:?}, got {:?}",
240self.expected, self.actual
241 )242 }
243}
244245impl std::error::Errorfor SexpTypeError {}
246247#[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"`.
254pub expected: &'static str,
255/// Actual R type encountered.
256pub actual: SEXPTYPE,
257}
258259impl std::fmt::Displayfor SexpClassError {
260fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261f.write_fmt(format_args!("class mismatch: expected {0}, got {1:?}",
self.expected, self.actual))write!(
262f,
263"class mismatch: expected {}, got {:?}",
264self.expected, self.actual
265 )266 }
267}
268269impl std::error::Errorfor SexpClassError {}
270271#[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.
275pub expected: usize,
276/// Actual length encountered.
277pub actual: usize,
278}
279280impl std::fmt::Displayfor SexpLengthError {
281fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
282f.write_fmt(format_args!("length mismatch: expected {0}, got {1}",
self.expected, self.actual))write!(
283f,
284"length mismatch: expected {}, got {}",
285self.expected, self.actual
286 )287 }
288}
289290impl std::error::Errorfor SexpLengthError {}
291292#[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.
296pub sexp_type: SEXPTYPE,
297}
298299impl std::fmt::Displayfor SexpNaError {
300fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
301f.write_fmt(format_args!("unexpected NA value in {0:?}", self.sexp_type))write!(f, "unexpected NA value in {:?}", self.sexp_type)302 }
303}
304305impl std::error::Errorfor SexpNaError {}
306307#[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.
311Type(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`.
315Class(SexpClassError),
316/// Length did not match the expected one.
317Length(SexpLengthError),
318/// Missing value encountered where disallowed.
319Na(SexpNaError),
320/// Value is syntactically valid but semantically invalid (e.g. parse error).
321InvalidValue(String),
322/// A required field was missing from a named list.
323MissingField(String),
324/// A named list has duplicate non-empty names.
325DuplicateName(String),
326/// A `match_arg` choice did not match (from `#[derive(MatchArg)]`'s
327 /// `TryFromSexp`), kept whole so the argument error names the choices.
328MatchArg(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.
344Condition(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")]
350EitherConversion {
351/// Error from attempting to convert to the Left type
352left_error: Box<SexpError>,
353/// Error from attempting to convert to the Right type
354right_error: Box<SexpError>,
355 },
356}
357358impl 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).
368pub(crate) fn r_reason(&self, expected_known: bool) -> String {
369let got = crate::typed_list::sexptype_name(self.actual);
370let hint = if self.expected == SEXPTYPE::VECSXP && self.actual == SEXPTYPE::LISTSXP {
371", convert it with as.list()"
372} else {
373""
374};
375if 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}",
380crate::typed_list::sexptype_name(self.expected)
381 )382 }
383 }
384}
385386impl 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`.
389pub(crate) fn r_reason(&self, expected_known: bool) -> String {
390let got = crate::typed_list::sexptype_name(self.actual);
391if 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}
398399impl 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`.
402pub(crate) fn r_reason(&self, expected_known: bool) -> String {
403if 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 {}",
408self.expected, self.actual
409 )410 }
411 }
412}
413414/// 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`.
420Noun(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.
424Choices(
425&'static [&'static str],
426&'static [crate::match_arg::MatchArgAlias],
427 ),
428}
429430#[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).
436fn join(&self, right: &KindExpected) -> String {
437use crate::match_arg::choice_expectation;
438match (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)) => {
444format!("{l} or {}", choice_expectation(r, aliases, false, ""))
445 }
446 (KindExpected::Choices(l, l_aliases), KindExpected::Choices(r, r_aliases)) => {
447let mut both: Vec<&str> = l.to_vec();
448 both.extend(r.iter().filter(|c| !l.contains(c)));
449let 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 }
459460/// This expectation on its own: the noun, or `one of "a", "b"`.
461fn text(&self) -> String {
462match self {
463 KindExpected::Noun(noun) => noun.clone(),
464 KindExpected::Choices(choices, aliases) => {
465crate::match_arg::choice_expectation(choices, aliases, false, "")
466 }
467 }
468 }
469}
470471/// 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`).
477Known(&'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).
482Opaque {
483/// `<T as TryFromSexp>::__MX_EXPECTATION`.
484declared: Option<&'a str>,
485/// The fallback name of the arm.
486noun: &'a str,
487 },
488/// An `Option<T>` or `Result<T, ()>` arm: `NULL or <T>`.
489Nullable(&'a ExpectedArm<'a>),
490/// A nested `Either`, matched with the two sides of its error.
491Either(&'a ExpectedArm<'a>, &'a ExpectedArm<'a>),
492}
493494#[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.
498fn resolve(&self, error: &SexpError) -> Option<(KindExpected, bool)> {
499match 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() {
509Some(expected) => (expected, true),
510None => (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) => {
519let SexpError::EitherConversion {
520 left_error,
521 right_error,
522 } = error
523else {
524return None;
525 };
526let (l, l_known) = left.resolve(left_error)?;
527let (r, r_known) = right.resolve(right_error)?;
528Some((KindExpected::Noun(l.join(&r)), l_known || r_known))
529 }
530 }
531 }
532}
533534/// 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> {
549match arms.resolve(error)? {
550 (expected, true) => Some(expected.text()),
551 (_, false) => None,
552 }
553}
554555impl 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.
565pub(crate) fn r_expectation(&self) -> Option<String> {
566match self {
567 SexpError::MatchArg(e) => Some(e.expectation()),
568 SexpError::Class(e) => Some(e.expected.to_string()),
569#[cfg(feature = "either")]
570SexpError::EitherConversion {
571 left_error,
572 right_error,
573 } => both_kind_mismatches(left_error, right_error).map(|(expected, _)| expected),
574_ => None,
575 }
576 }
577578/// 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")]
585fn kind_mismatch(&self) -> Option<(KindExpected, SEXPTYPE)> {
586match 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 }
605606/// 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")]
612fn arm_expectation(&self) -> Option<KindExpected> {
613match self {
614 SexpError::MatchArg(e) => Some(KindExpected::Choices(e.choices(), e.aliases())),
615_ => self.kind_mismatch().map(|(expected, _)| expected),
616 }
617 }
618619/// 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 `; `.
654pub(crate) fn r_reason(&self, expected_known: bool) -> String {
655match 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")]
666SexpError::EitherConversion {
667 left_error,
668 right_error,
669 } => {
670let (l, r) = (left_error.as_ref(), right_error.as_ref());
671if let Some((expected, actual)) = both_kind_mismatches(l, r) {
672let got = crate::typed_list::sexptype_name(actual);
673if expected_known {
674format!("got {got}")
675 } else {
676format!("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 {
683let (l, r) = (l.r_reason(expected_known), r.r_reason(expected_known));
684if l == r { l } else { format!("{l}; {r}") }
685 }
686 }
687 }
688 }
689690/// 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.
695pub(crate) fn reported_conditions(&self) -> Vec<&crate::condition::RError> {
696match 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")]
699SexpError::EitherConversion {
700 left_error,
701 right_error,
702 } => {
703let (l, r) = (left_error.as_ref(), right_error.as_ref());
704if 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 {
711let mut both = l.reported_conditions();
712 both.extend(r.reported_conditions());
713 both
714 }
715 }
716_ => Vec::new(),
717 }
718 }
719}
720721/// 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)> {
726let (l, actual) = left.kind_mismatch()?;
727let (r, _) = right.kind_mismatch()?;
728Some((l.join(&r), actual))
729}
730731impl std::fmt::Displayfor SexpError {
732fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
733match 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")]
744SexpError::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}
755756impl std::error::Errorfor SexpError {
757fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
758match 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).
768SexpError::Condition(_) => None,
769#[cfg(feature = "either")]
770SexpError::EitherConversion { .. } => None,
771 }
772 }
773}
774775impl From<SexpTypeError> for SexpError {
776fn from(e: SexpTypeError) -> Self {
777 SexpError::Type(e)
778 }
779}
780781impl From<SexpClassError> for SexpError {
782fn from(e: SexpClassError) -> Self {
783 SexpError::Class(e)
784 }
785}
786787impl From<SexpLengthError> for SexpError {
788fn from(e: SexpLengthError) -> Self {
789 SexpError::Length(e)
790 }
791}
792793impl From<SexpNaError> for SexpError {
794fn from(e: SexpNaError) -> Self {
795 SexpError::Na(e)
796 }
797}
798799pub mod borrow;
800pub use borrow::NativeBorrow;
801802/// 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.
826const NATIVE_BORROW: Option<NativeBorrow> = None;
827828/// 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 /// ```
869const CHARACTER_ONLY: bool = false;
870871/// 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)]
882const __MX_EXPECTATION: Option<&'static str> = None;
883884/// The error type returned when conversion fails.
885type Error;
886887/// 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.
890fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error>;
891892/// 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.
899unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
900// Default: just call the checked version
901Self::try_from_sexp(sexp)
902 }
903904/// 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)]
911unsafe fn __mx_try_from_rooted(root: crate::Root<'_>) -> Result<Self, Self::Error> {
912Self::try_from_sexp(root.get())
913 }
914915/// 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]
929fn __mx_has_na(&self) -> bool {
930false
931}
932933/// 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]
959fn __mx_input_has_na(&self, input: SEXP) -> bool {
960any_na(input) || self.__mx_has_na()
961 }
962963/// 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]
985fn __mx_try_from_sexp_with_check(
986 sexp: SEXP,
987 check: &ElementCheck<'_>,
988 ) -> Result<Self, SexpError>
989where
990Self::Error: Into<SexpError>,
991 {
992 check(sexp)?;
993Self::try_from_sexp(sexp).map_err(Into::into)
994 }
995996/// [`__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]
1005unsafe fn __mx_try_from_sexp_unchecked_with_check(
1006 sexp: SEXP,
1007 check: &ElementCheck<'_>,
1008 ) -> Result<Self, SexpError>
1009where
1010Self::Error: Into<SexpError>,
1011 {
1012 check(sexp)?;
1013unsafe { Self::try_from_sexp_unchecked(sexp) }.map_err(Into::into)
1014 }
1015}
10161017/// 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;
10221023/// 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> {
1028Ok(())
1029}
10301031// 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.
10381039impl<T> TryFromSexpfor Box<[T]>
1040where
1041Vec<T>: TryFromSexp,
1042{
1043const NATIVE_BORROW: Option<NativeBorrow> = <Vec<T> as TryFromSexp>::NATIVE_BORROW;
1044const CHARACTER_ONLY: bool = <Vec<T> as TryFromSexp>::CHARACTER_ONLY;
10451046type Error = <Vec<T> as TryFromSexp>::Error;
10471048#[inline]
1049fn 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 }
10521053#[inline]
1054unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1055unsafe { <Vec<T> as TryFromSexp>::try_from_sexp_unchecked(sexp) }
1056 .map(|v| v.into_boxed_slice())
1057 }
1058}
1059// endregion
10601061macro_rules!impl_try_from_sexp_scalar_native {
1062 ($t:ty, $sexptype:ident) => {
1063impl TryFromSexp for $t {
1064type Error = SexpError;
10651066#[inline]
1067fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1068let actual = sexp.type_of();
1069if actual != SEXPTYPE::$sexptype {
1070return Err(SexpTypeError {
1071 expected: SEXPTYPE::$sexptype,
1072 actual,
1073 }
1074 .into());
1075 }
1076let len = sexp.len();
1077if len != 1 {
1078return Err(SexpLengthError {
1079 expected: 1,
1080 actual: len,
1081 }
1082 .into());
1083 }
1084unsafe { 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 }
10951096#[inline]
1097unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1098let actual = sexp.type_of();
1099if actual != SEXPTYPE::$sexptype {
1100return Err(SexpTypeError {
1101 expected: SEXPTYPE::$sexptype,
1102 actual,
1103 }
1104 .into());
1105 }
1106let len = unsafe { sexp.len_unchecked() };
1107if len != 1 {
1108return Err(SexpLengthError {
1109 expected: 1,
1110 actual: len,
1111 }
1112 .into());
1113 }
1114unsafe { 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}
11281129// 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 TryFromSexpfor i32 {
1132type Error = SexpError;
11331134#[inline]
1135fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1136let actual = sexp.type_of();
1137if actual != SEXPTYPE::INTSXP {
1138return Err(SexpTypeError {
1139 expected: SEXPTYPE::INTSXP,
1140actual,
1141 }
1142 .into());
1143 }
1144let len = sexp.len();
1145if len != 1 {
1146return Err(SexpLengthError {
1147 expected: 1,
1148 actual: len,
1149 }
1150 .into());
1151 }
1152let 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 })?;
1161if v.is_na() {
1162return Err(SexpNaError {
1163 sexp_type: SEXPTYPE::INTSXP,
1164 }
1165 .into());
1166 }
1167Ok(v)
1168 }
11691170#[inline]
1171unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1172let actual = sexp.type_of();
1173if actual != SEXPTYPE::INTSXP {
1174return Err(SexpTypeError {
1175 expected: SEXPTYPE::INTSXP,
1176actual,
1177 }
1178 .into());
1179 }
1180let len = unsafe { sexp.len_unchecked() };
1181if len != 1 {
1182return Err(SexpLengthError {
1183 expected: 1,
1184 actual: len,
1185 }
1186 .into());
1187 }
1188let 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 })?;
1197if v.is_na() {
1198return Err(SexpNaError {
1199 sexp_type: SEXPTYPE::INTSXP,
1200 }
1201 .into());
1202 }
1203Ok(v)
1204 }
1205}
12061207impl 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);
12111212/// 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 TryFromSexpfor SEXP {
1237type Error = SexpError;
12381239/// 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]
1246fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1247Ok(unsafe { crate::altrep_sexp::ensure_materialized(sexp) })
1248 }
12491250#[inline]
1251unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1252Ok(unsafe { crate::altrep_sexp::ensure_materialized(sexp) })
1253 }
1254}
12551256impl TryFromSexpfor Option<SEXP> {
1257type Error = SexpError;
12581259#[inline]
1260fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1261if sexp.type_of() == SEXPTYPE::NILSXP {
1262Ok(None)
1263 } else {
1264Ok(Some(unsafe {
1265crate::altrep_sexp::ensure_materialized(sexp)
1266 }))
1267 }
1268 }
12691270#[inline]
1271unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1272Self::try_from_sexp(sexp)
1273 }
1274}
1275// endregion
12761277mod logical;
12781279mod coerced_scalars;
1280pub(crate) use coerced_scalars::{coerce_real_value, coerce_value};
12811282mod references;
12831284// region: Blanket implementations for slices with arbitrary lifetimes
12851286/// 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> TryFromSexpfor &[T]
1292where
1293T: crate::RNativeType + Copy,
1294{
1295const NATIVE_BORROW: Option<NativeBorrow> = Some(NativeBorrow::slice(T::SEXP_TYPE, false));
12961297type Error = SexpTypeError;
12981299#[inline]
1300fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1301let actual = sexp.type_of();
1302if actual != T::SEXP_TYPE {
1303return Err(SexpTypeError {
1304 expected: T::SEXP_TYPE,
1305actual,
1306 });
1307 }
1308Ok(unsafe { sexp.as_slice::<T>() })
1309 }
13101311#[inline]
1312unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1313let actual = sexp.type_of();
1314if actual != T::SEXP_TYPE {
1315return Err(SexpTypeError {
1316 expected: T::SEXP_TYPE,
1317actual,
1318 });
1319 }
1320Ok(unsafe { sexp.as_slice_unchecked::<T>() })
1321 }
1322}
13231324/// 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> TryFromSexpfor &mut [T]
1337where
1338T: crate::RNativeType + Copy,
1339{
1340const NATIVE_BORROW: Option<NativeBorrow> = Some(NativeBorrow::slice(T::SEXP_TYPE, true));
13411342type Error = SexpTypeError;
13431344#[inline]
1345fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1346let actual = sexp.type_of();
1347if actual != T::SEXP_TYPE {
1348return Err(SexpTypeError {
1349 expected: T::SEXP_TYPE,
1350actual,
1351 });
1352 }
1353let len = sexp.len();
1354let ptr = unsafe { T::dataptr_mut(sexp) };
1355Ok(unsafe { r_slice_mut(ptr, len) })
1356 }
13571358#[inline]
1359unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1360let actual = sexp.type_of();
1361if actual != T::SEXP_TYPE {
1362return Err(SexpTypeError {
1363 expected: T::SEXP_TYPE,
1364actual,
1365 });
1366 }
1367let len = unsafe { sexp.len_unchecked() };
1368let ptr = unsafe { T::dataptr_mut(sexp) };
1369Ok(unsafe { r_slice_mut(ptr, len) })
1370 }
1371}
13721373/// Blanket impl for `Option<&[T]>` where T: RNativeType
1374impl<T> TryFromSexpfor Option<&[T]>
1375where
1376T: crate::RNativeType + Copy,
1377{
1378const NATIVE_BORROW: Option<NativeBorrow> = <&[T] as TryFromSexp>::NATIVE_BORROW;
13791380type Error = SexpError;
13811382#[inline]
1383fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1384if sexp.type_of() == SEXPTYPE::NILSXP {
1385return Ok(None);
1386 }
1387let slice: &[T] = TryFromSexp::try_from_sexp(sexp).map_err(SexpError::from)?;
1388Ok(Some(slice))
1389 }
13901391#[inline]
1392unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1393if sexp.type_of() == SEXPTYPE::NILSXP {
1394return Ok(None);
1395 }
1396let slice: &[T] =
1397unsafe { TryFromSexp::try_from_sexp_unchecked(sexp).map_err(SexpError::from)? };
1398Ok(Some(slice))
1399 }
1400}
14011402/// Blanket impl for `Option<&mut [T]>` where T: RNativeType
1403impl<T> TryFromSexpfor Option<&mut [T]>
1404where
1405T: crate::RNativeType + Copy,
1406{
1407const NATIVE_BORROW: Option<NativeBorrow> = <&mut [T] as TryFromSexp>::NATIVE_BORROW;
14081409type Error = SexpError;
14101411#[inline]
1412fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1413if sexp.type_of() == SEXPTYPE::NILSXP {
1414return Ok(None);
1415 }
1416let slice: &mut [T] = TryFromSexp::try_from_sexp(sexp).map_err(SexpError::from)?;
1417Ok(Some(slice))
1418 }
14191420#[inline]
1421unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1422if sexp.type_of() == SEXPTYPE::NILSXP {
1423return Ok(None);
1424 }
1425let slice: &mut [T] =
1426unsafe { TryFromSexp::try_from_sexp_unchecked(sexp).map_err(SexpError::from)? };
1427Ok(Some(slice))
1428 }
1429}
1430// endregion
14311432mod strings;
14331434mod str_parse;
1435pub use str_parse::{ParseRStr, ParsedRStr};
14361437// region: Result conversions (NULL -> Err(()))
14381439impl<T> TryFromSexpfor Result<T, ()>
1440where
1441T: TryFromSexp,
1442 T::Error: Into<SexpError>,
1443{
1444const NATIVE_BORROW: Option<crate::from_r::NativeBorrow> = T::NATIVE_BORROW;
14451446type Error = SexpError;
1447// `NULL` (`Err(())`) is "not given" and passes `no_na`.
1448#[inline]
1449fn __mx_has_na(&self) -> bool {
1450self.as_ref().is_ok_and(T::__mx_has_na)
1451 }
14521453// 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]
1456fn __mx_input_has_na(&self, input: SEXP) -> bool {
1457match self {
1458Ok(v) => T::__mx_input_has_na(v, input),
1459Err(()) => false,
1460 }
1461 }
14621463#[inline]
1464fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1465if sexp.type_of() == SEXPTYPE::NILSXP {
1466return Ok(Err(()));
1467 }
1468let value = T::try_from_sexp(sexp).map_err(Into::into)?;
1469Ok(Ok(value))
1470 }
14711472#[inline]
1473unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1474if sexp.type_of() == SEXPTYPE::NILSXP {
1475return Ok(Err(()));
1476 }
1477let value = unsafe { T::try_from_sexp_unchecked(sexp).map_err(Into::into)? };
1478Ok(Ok(value))
1479 }
1480}
1481// endregion
14821483mod na_vectors;
14841485mod collections;
14861487mod tuples;
14881489// region: Fixed-size array conversions
14901491/// 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> TryFromSexpfor [T; N]
1496where
1497T: crate::RNativeType + Copy,
1498{
1499type Error = SexpError;
15001501fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1502let slice: &[T] = TryFromSexp::try_from_sexp(sexp)?;
1503if slice.len() != N {
1504return Err(SexpLengthError {
1505 expected: N,
1506 actual: slice.len(),
1507 }
1508 .into());
1509 }
15101511// T: Copy, length verified above. Use MaybeUninit + copy_from_slice.
1512let mut arr = std::mem::MaybeUninit::<[T; N]>::uninit();
1513unsafe {
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.
1516let dst: &mut [T] = std::slice::from_raw_parts_mut(arr.as_mut_ptr().cast::<T>(), N);
1517dst.copy_from_slice(&slice[..N]);
1518Ok(arr.assume_init())
1519 }
1520 }
15211522unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1523Self::try_from_sexp(sexp)
1524 }
1525}
1526// endregion
15271528// region: VecDeque conversions
15291530use std::collections::VecDeque;
15311532/// Blanket impl: Convert R vector to `VecDeque<T>` where T: RNativeType.
1533impl<T> TryFromSexpfor VecDeque<T>
1534where
1535T: crate::RNativeType + Copy,
1536{
1537type Error = SexpTypeError;
15381539fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1540let slice: &[T] = TryFromSexp::try_from_sexp(sexp)?;
1541Ok(VecDeque::from(slice.to_vec()))
1542 }
15431544unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1545let slice: &[T] = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
1546Ok(VecDeque::from(slice.to_vec()))
1547 }
1548}
1549// endregion
15501551// region: BinaryHeap conversions
15521553use std::collections::BinaryHeap;
15541555/// 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> TryFromSexpfor BinaryHeap<T>
1559where
1560T: crate::RNativeType + Copy + Ord,
1561{
1562type Error = SexpTypeError;
15631564fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1565let slice: &[T] = TryFromSexp::try_from_sexp(sexp)?;
1566Ok(BinaryHeap::from(slice.to_vec()))
1567 }
15681569unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1570let slice: &[T] = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
1571Ok(BinaryHeap::from(slice.to_vec()))
1572 }
1573}
1574// endregion
15751576mod cow_and_paths;
1577mod rooted;
1578pub use rooted::TryFromRoot;
15791580// 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.
15841585/// Convert R value to `Option<Vec<T>>`: NULL → None, otherwise Some(vec).
1586impl<T> TryFromSexpfor Option<Vec<T>>
1587where
1588Vec<T>: TryFromSexp,
1589 <Vec<T> as TryFromSexp>::Error: Into<SexpError>,
1590{
1591const NATIVE_BORROW: Option<NativeBorrow> = <Vec<T> as TryFromSexp>::NATIVE_BORROW;
1592const CHARACTER_ONLY: bool = <Vec<T> as TryFromSexp>::CHARACTER_ONLY;
15931594type Error = SexpError;
15951596#[inline]
1597fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1598if sexp.type_of() == SEXPTYPE::NILSXP {
1599Ok(None)
1600 } else {
1601Vec::<T>::try_from_sexp(sexp).map(Some).map_err(Into::into)
1602 }
1603 }
16041605#[inline]
1606unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1607if sexp.type_of() == SEXPTYPE::NILSXP {
1608Ok(None)
1609 } else {
1610unsafe {
1611Vec::<T>::try_from_sexp_unchecked(sexp)
1612 .map(Some)
1613 .map_err(Into::into)
1614 }
1615 }
1616 }
1617}
16181619macro_rules!impl_option_map_try_from_sexp {
1620 ($(#[$meta:meta])* $map_ty:ident) => {
1621 $(#[$meta])*
1622impl<V: TryFromSexp> TryFromSexp for Option<$map_ty<String, V>>
1623where
1624V::Error: Into<SexpError>,
1625 {
1626const NATIVE_BORROW: Option<NativeBorrow> =
1627 <$map_ty<String, V> as TryFromSexp>::NATIVE_BORROW;
16281629type Error = SexpError;
1630#[inline]
1631fn __mx_has_na(&self) -> bool {
1632self.as_ref()
1633 .is_some_and(<$map_ty<String, V> as TryFromSexp>::__mx_has_na)
1634 }
16351636#[inline]
1637fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1638if sexp.type_of() == SEXPTYPE::NILSXP {
1639Ok(None)
1640 } else {
1641$map_ty::<String, V>::try_from_sexp(sexp).map(Some)
1642 }
1643 }
1644 }
1645 };
1646}
16471648#[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).
1650HashMap
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).
1654BTreeMap
1655);
16561657macro_rules!impl_option_set_try_from_sexp {
1658 ($(#[$meta:meta])* $set_ty:ident) => {
1659 $(#[$meta])*
1660impl<T> TryFromSexp for Option<$set_ty<T>>
1661where
1662$set_ty<T>: TryFromSexp,
1663 <$set_ty<T> as TryFromSexp>::Error: Into<SexpError>,
1664 {
1665const CHARACTER_ONLY: bool = <$set_ty<T> as TryFromSexp>::CHARACTER_ONLY;
16661667type Error = SexpError;
16681669#[inline]
1670fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1671if sexp.type_of() == SEXPTYPE::NILSXP {
1672Ok(None)
1673 } else {
1674$set_ty::<T>::try_from_sexp(sexp)
1675 .map(Some)
1676 .map_err(Into::into)
1677 }
1678 }
1679 }
1680 };
1681}
16821683#[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).
1685HashSet
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).
1689BTreeSet
1690);
1691// endregion
16921693// region: Nested vector conversions (list of vectors)
16941695/// Convert R list (VECSXP) to `Vec<Vec<T>>`.
1696///
1697/// Each element of the R list must be convertible to `Vec<T>`.
1698impl<T> TryFromSexpfor Vec<Vec<T>>
1699where
1700Vec<T>: TryFromSexp,
1701 <Vec<T> as TryFromSexp>::Error: Into<SexpError>,
1702{
1703const NATIVE_BORROW: Option<NativeBorrow> =
1704NativeBorrow::in_list(<Vec<T> as TryFromSexp>::NATIVE_BORROW);
17051706type Error = SexpError;
17071708fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1709map_vecsxp_with(sexp, |_i, elem| {
1710Vec::<T>::try_from_sexp(elem).map_err(Into::into)
1711 })
1712 }
17131714unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1715unsafe {
1716map_vecsxp_with_unchecked(sexp, |_i, elem| {
1717Vec::<T>::try_from_sexp_unchecked(elem).map_err(Into::into)
1718 })
1719 }
1720 }
1721}
1722// endregion
17231724// region: Coerced wrapper - bridge between TryFromSexp and TryCoerce
17251726use crate::coerce::Coerced;
17271728/// 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> TryFromSexpfor Coerced<T, R>
1742where
1743R: TryFromSexp,
1744 R: TryCoerce<T>,
1745 <R as TryFromSexp>::Error: Into<SexpError>,
1746 <R as TryCoerce<T>>::Error: std::fmt::Display,
1747{
1748type Error = SexpError;
17491750#[inline]
1751fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1752let r_val: R = R::try_from_sexp(sexp).map_err(Into::into)?;
1753let 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}")))?;
1756Ok(Coerced::new(value))
1757 }
17581759#[inline]
1760unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1761let r_val: R = unsafe { R::try_from_sexp_unchecked(sexp).map_err(Into::into)? };
1762let 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}")))?;
1765Ok(Coerced::new(value))
1766 }
1767}
1768// endregion
17691770/// 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> {
1775if sexp.type_of() == SEXPTYPE::INTSXP {
1776Coerced::<bool, i32>::try_from_sexp(sexp).map(Coerced::into_inner)
1777 } else {
1778bool::try_from_sexp(sexp)
1779 }
1780}
17811782/// 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> {
1785if sexp.type_of() == SEXPTYPE::INTSXP {
1786let slice: &[i32] = TryFromSexp::try_from_sexp(sexp)?;
1787coerce_slice_to_vec(slice)
1788 } else {
1789Vec::<bool>::try_from_sexp(sexp)
1790 }
1791}
17921793/// 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> {
1801if sexp.type_of() == SEXPTYPE::INTSXP {
1802i32::try_from_sexp(sexp)
1803 } else {
1804 coerced_scalars::try_from_sexp_numeric_scalar(sexp)
1805 }
1806}
18071808/// 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> {
1816match sexp.type_of() {
1817 SEXPTYPE::INTSXP => {
1818let v: Option<i32> = TryFromSexp::try_from_sexp(sexp)?;
1819Ok(v.map_or(NA_REAL, f64::from))
1820 }
1821 SEXPTYPE::LGLSXP => {
1822let v: RLogical = TryFromSexp::try_from_sexp(sexp)?;
1823Ok(if v.is_na() {
1824NA_REAL1825 } else {
1826f64::from(v.to_i32())
1827 })
1828 }
1829 SEXPTYPE::RAWSXP => {
1830let v: u8 = TryFromSexp::try_from_sexp(sexp)?;
1831Ok(f64::from(v))
1832 }
1833_ => f64::try_from_sexp(sexp),
1834 }
1835}
18361837/// 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> {
1845if sexp.type_of() == SEXPTYPE::INTSXP {
1846return Vec::<i32>::try_from_sexp(sexp).map_err(SexpError::from);
1847 }
1848from_numeric_vec_with(
1849sexp,
1850Ok,
1851 |v: f64| {
1852if v.is_na() {
1853Ok(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}
18631864/// 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> {
1870if sexp.type_of() == SEXPTYPE::REALSXP {
1871return Vec::<f64>::try_from_sexp(sexp).map_err(SexpError::from);
1872 }
1873from_numeric_vec_with(
1874sexp,
1875 |v: i32| Ok(if v.is_na() { NA_REAL } else { f64::from(v) }),
1876Ok,
1877 |v: u8| Ok(f64::from(v)),
1878 |v: RLogical| {
1879Ok(if v.is_na() {
1880NA_REAL1881 } else {
1882f64::from(v.to_i32())
1883 })
1884 },
1885 )
1886}
18871888// 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.
18931894/// 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
1904R: Copy + TryCoerce<T>,
1905 <R as TryCoerce<T>>::Error: std::fmt::Display,
1906{
1907let mut result = Vec::with_capacity(slice.len());
1908let mut errors = BatchedErrors::default();
1909for (i, v) in slice.iter().copied().enumerate() {
1910match v.try_coerce() {
1911Ok(x) => result.push(x),
1912Err(e) => errors.push(i, || e.to_string()),
1913 }
1914 }
1915if errors.is_empty() {
1916Ok(result)
1917 } else {
1918Err(errors.into_element_error())
1919 }
1920}
19211922/// 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> {
1936let mut result = Vec::with_capacity(len);
1937let mut errors = BatchedErrors::default();
1938for (i, item) in iter.enumerate() {
1939match item {
1940Ok(v) => result.push(v),
1941Err(e) => errors.push(i, || e.r_reason(false)),
1942 }
1943 }
1944if errors.is_empty() {
1945Ok(result)
1946 } else {
1947Err(errors.into_element_error())
1948 }
1949}
19501951/// 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
1973FI: 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{
1978let actual = sexp.type_of();
1979match actual {
1980 SEXPTYPE::INTSXP => {
1981let slice: &[i32] = unsafe { sexp.as_slice() };
1982collect_coerced(slice.len(), slice.iter().copied().map(map_i32))
1983 }
1984 SEXPTYPE::REALSXP => {
1985let slice: &[f64] = unsafe { sexp.as_slice() };
1986collect_coerced(slice.len(), slice.iter().copied().map(map_f64))
1987 }
1988 SEXPTYPE::RAWSXP => {
1989let slice: &[u8] = unsafe { sexp.as_slice() };
1990collect_coerced(slice.len(), slice.iter().copied().map(map_u8))
1991 }
1992 SEXPTYPE::LGLSXP => {
1993let slice: &[RLogical] = unsafe { sexp.as_slice() };
1994collect_coerced(slice.len(), slice.iter().copied().map(map_lgl))
1995 }
1996_ => Err(SexpTypeError {
1997 expected: SEXPTYPE::REALSXP,
1998actual,
1999 }
2000 .into()),
2001 }
2002}
20032004/// 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,
2021mut map: impl FnMut(SEXP/* charsxp */, usize) -> Result<U, SexpError>,
2022) -> Result<Vec<U>, SexpError> {
2023let actual = sexp.type_of();
2024if actual != SEXPTYPE::STRSXP {
2025return Err(SexpTypeError {
2026 expected: SEXPTYPE::STRSXP,
2027actual,
2028 }
2029 .into());
2030 }
20312032let len = sexp.len();
2033let mut result = Vec::with_capacity(len);
2034for i in 0..len {
2035let charsxp = sexp.string_elt(i as crate::R_xlen_t);
2036 result.push(map(charsxp, i)?);
2037 }
2038Ok(result)
2039}
20402041/// 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 {
2052let ty = value.type_of();
2053if ty == SEXPTYPE::NILSXP {
2054return "got NULL".to_string();
2055 }
2056let 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 );
2065if !atomic {
2066return ::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 }
2068if value.len() == 0 {
2069return "got length 0".to_string();
2070 }
2071if ty == SEXPTYPE::STRSXP {
2072let charsxp = value.string_elt(0);
2073if charsxp != SEXP::na_string() {
2074// SAFETY: a non-NA CHARSXP of a live STRSXP.
2075return ::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}
20802081/// 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> {
2090let actual = sexp.type_of();
2091if actual != SEXPTYPE::STRSXP {
2092return Err(SexpTypeError {
2093 expected: SEXPTYPE::STRSXP,
2094actual,
2095 }
2096 .into());
2097 }
20982099let len = sexp.len();
2100if len != 1 {
2101return Err(SexpLengthError {
2102 expected: 1,
2103 actual: len,
2104 }
2105 .into());
2106 }
21072108Ok(sexp.string_elt(0))
2109}
21102111/// 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> {
2120let actual = sexp.type_of();
2121if actual != SEXPTYPE::STRSXP {
2122return Err(SexpTypeError {
2123 expected: SEXPTYPE::STRSXP,
2124actual,
2125 }
2126 .into());
2127 }
21282129let len = unsafe { sexp.len_unchecked() };
2130if len != 1 {
2131return Err(SexpLengthError {
2132 expected: 1,
2133 actual: len,
2134 }
2135 .into());
2136 }
21372138Ok(unsafe { sexp.string_elt_unchecked(0) })
2139}
21402141/// 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,
2151mut map: impl FnMut(usize, SEXP) -> Result<U, SexpError>,
2152) -> Result<Vec<U>, SexpError> {
2153let actual = sexp.type_of();
2154if actual != SEXPTYPE::VECSXP {
2155return Err(SexpTypeError {
2156 expected: SEXPTYPE::VECSXP,
2157actual,
2158 }
2159 .into());
2160 }
21612162let len = sexp.len();
2163let mut result = Vec::with_capacity(len);
2164for i in 0..len {
2165let elem = sexp.vector_elt(i as crate::R_xlen_t);
2166 result.push(map(i, elem)?);
2167 }
2168Ok(result)
2169}
21702171/// 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,
2181mut map: impl FnMut(usize, SEXP) -> Result<U, SexpError>,
2182) -> Result<Vec<U>, SexpError> {
2183let actual = sexp.type_of();
2184if actual != SEXPTYPE::VECSXP {
2185return Err(SexpTypeError {
2186 expected: SEXPTYPE::VECSXP,
2187actual,
2188 }
2189 .into());
2190 }
21912192let len = unsafe { sexp.len_unchecked() };
2193let mut result = Vec::with_capacity(len);
2194for i in 0..len {
2195let elem = unsafe { sexp.vector_elt_unchecked(i as crate::R_xlen_t) };
2196 result.push(map(i, elem)?);
2197 }
2198Ok(result)
2199}
22002201/// 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
2212i32: TryCoerce<T>,
2213f64: TryCoerce<T>,
2214u8: TryCoerce<T>,
2215 <i32as TryCoerce<T>>::Error: std::fmt::Display,
2216 <f64as TryCoerce<T>>::Error: std::fmt::Display,
2217 <u8as TryCoerce<T>>::Error: std::fmt::Display,
2218{
2219from_numeric_vec_with(
2220sexp,
2221 |v: i32| {
2222if v.is_na() {
2223Err(SexpNaError {
2224 sexp_type: SEXPTYPE::INTSXP,
2225 }
2226 .into())
2227 } else {
2228coerce_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.)
2233coerce_real_value,
2234coerce_value,
2235 |v: RLogical| {
2236if v.is_na() {
2237Err(SexpNaError {
2238 sexp_type: SEXPTYPE::LGLSXP,
2239 }
2240 .into())
2241 } else {
2242coerce_value(v.to_i32())
2243 }
2244 },
2245 )
2246}
22472248/// Implement `TryFromSexp for Vec<$target>` by coercing from integer/real/logical/raw.
2249macro_rules!impl_vec_try_from_sexp_numeric {
2250 ($target:ty) => {
2251impl TryFromSexp for Vec<$target> {
2252type Error = SexpError;
22532254fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2255 try_from_sexp_numeric_vec(sexp)
2256 }
22572258unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2259 try_from_sexp_numeric_vec(sexp)
2260 }
2261 }
2262 };
2263}
22642265impl 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);
22742275/// Convert R logical vector (LGLSXP) to `Vec<bool>` (errors on NA).
2276impl TryFromSexpfor Vec<bool> {
2277type Error = SexpError;
22782279fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2280let actual = sexp.type_of();
2281if actual != SEXPTYPE::LGLSXP {
2282return Err(SexpTypeError {
2283 expected: SEXPTYPE::LGLSXP,
2284actual,
2285 }
2286 .into());
2287 }
2288let slice: &[RLogical] = unsafe { sexp.as_slice() };
2289coerce_slice_to_vec(slice)
2290 }
22912292unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2293Self::try_from_sexp(sexp)
2294 }
2295}
2296// endregion
22972298// region: Direct HashSet / BTreeSet coercion conversions
22992300/// 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
2306S: std::iter::FromIterator<T>,
2307i32: TryCoerce<T>,
2308f64: TryCoerce<T>,
2309u8: TryCoerce<T>,
2310 <i32as TryCoerce<T>>::Error: std::fmt::Display,
2311 <f64as TryCoerce<T>>::Error: std::fmt::Display,
2312 <u8as TryCoerce<T>>::Error: std::fmt::Display,
2313{
2314let vec = try_from_sexp_numeric_vec(sexp)?;
2315Ok(vec.into_iter().collect())
2316}
23172318macro_rules!impl_set_try_from_sexp_numeric {
2319 ($set_ty:ident, $target:ty) => {
2320impl TryFromSexp for $set_ty<$target> {
2321type Error = SexpError;
23222323fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2324 try_from_sexp_numeric_set(sexp)
2325 }
23262327unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2328 try_from_sexp_numeric_set(sexp)
2329 }
2330 }
2331 };
2332}
23332334impl 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);
23422343impl 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);
23512352macro_rules!impl_set_try_from_sexp_bool {
2353 ($set_ty:ident) => {
2354impl TryFromSexp for $set_ty<bool> {
2355type Error = SexpError;
23562357fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2358let vec: Vec<bool> = TryFromSexp::try_from_sexp(sexp)?;
2359Ok(vec.into_iter().collect())
2360 }
23612362unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2363Self::try_from_sexp(sexp)
2364 }
2365 }
2366 };
2367}
23682369impl 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
23722373// region: ExternalPtr conversions
23742375use crate::externalptr::{ExternalPtr, TypeMismatchError, TypedExternal};
23762377/// 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 {
2381match 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.
2394TypeMismatchError::Restored { .. } | TypeMismatchError::OtherVersion { .. } => {
2395let message = e.to_string();
2396let classes: Vec<String> = e2397 .restored_classes()
2398 .map(|classes| classes.iter().map(|&class| class.to_owned()).collect())
2399 .unwrap_or_default();
2400let mut error = crate::condition::RError::new(message.clone())
2401 .class(classes)
2402 .argument_message(message);
2403if let TypeMismatchError::OtherVersion { saved, current, .. } = e {
2404error = error2405 .data("saved_version", saved)
2406 .data("current_version", current);
2407 }
2408 SexpError::Condition(error)
2409 }
2410 }
2411}
24122413/// 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}
24232424/// 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> {
2431let actual = sexp.type_of();
2432if actual != SEXPTYPE::EXTPTRSXP {
2433return match unsafe { crate::externalptr::unwrap_class_handle(sexp) } {
2434// SAFETY: on R's main thread, like every conversion from R.
2435Some(inner) => unsafe { ExternalPtr::wrap_sexp_with_error(inner) }
2436 .map(|ptr| unsafe { ptr.rooted() })
2437 .map_err(type_mismatch_to_sexp_error),
2438None => Err(not_a_handle_error(actual)),
2439 };
2440 }
24412442// Use ExternalPtr's type-checked constructor
2443unsafe { ExternalPtr::wrap_sexp_with_error(sexp) }.map_err(type_mismatch_to_sexp_error)
2444}
24452446/// 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> {
2454external_ptr_from_sexp(sexp)
2455}
24562457/// [`__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> {
2463if sexp.type_of() == SEXPTYPE::NILSXP {
2464return Ok(None);
2465 }
2466external_ptr_from_sexp(sexp).map(Some)
2467}
24682469/// 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> TryFromSexpfor ExternalPtr<T> {
2487type Error = SexpError;
24882489fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2490external_ptr_from_sexp(sexp)
2491 }
24922493unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2494let actual = sexp.type_of();
2495if actual != SEXPTYPE::EXTPTRSXP {
2496return match unsafe { crate::externalptr::unwrap_class_handle(sexp) } {
2497Some(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 }),
2506None => Err(not_a_handle_error(actual)),
2507 };
2508 }
25092510// Use ExternalPtr's type-checked constructor (unchecked variant)
2511unsafe { 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}
25182519impl<T: TypedExternal + Send> TryFromSexpfor Option<ExternalPtr<T>> {
2520type Error = SexpError;
25212522fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2523if sexp.type_of() == SEXPTYPE::NILSXP {
2524return Ok(None);
2525 }
2526let ptr: ExternalPtr<T> = TryFromSexp::try_from_sexp(sexp)?;
2527Ok(Some(ptr))
2528 }
25292530unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2531if sexp.type_of() == SEXPTYPE::NILSXP {
2532return Ok(None);
2533 }
2534let ptr: ExternalPtr<T> = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
2535Ok(Some(ptr))
2536 }
2537}
25382539/// 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> TryFromSexpfor Vec<ExternalPtr<T>> {
2549type Error = SexpError;
25502551fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2552map_vecsxp_with(sexp, |_i, elem| {
2553 <ExternalPtr<T> as TryFromSexp>::try_from_sexp(elem)
2554 })
2555 }
25562557unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2558unsafe {
2559map_vecsxp_with_unchecked(sexp, |_i, elem| {
2560 <ExternalPtr<T> as TryFromSexp>::try_from_sexp_unchecked(elem)
2561 })
2562 }
2563 }
2564}
25652566/// 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> TryFromSexpfor Vec<Option<ExternalPtr<T>>> {
2570type Error = SexpError;
25712572fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2573map_vecsxp_with(sexp, |_i, elem| {
2574 <Option<ExternalPtr<T>> as TryFromSexp>::try_from_sexp(elem)
2575 })
2576 }
25772578unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2579unsafe {
2580map_vecsxp_with_unchecked(sexp, |_i, elem| {
2581 <Option<ExternalPtr<T>> as TryFromSexp>::try_from_sexp_unchecked(elem)
2582 })
2583 }
2584 }
2585}
2586// endregion
25872588// region: R connections — TryFromSexp impls (issue #175, #176)
25892590#[cfg(feature = "connections")]
2591mod connections_from_r {
2592use crate::SEXP;
2593use crate::connection::access::{self, ConnState};
2594use crate::connection::{RNullConnection, RStderr, RStdin, RStdout};
2595use crate::from_r::{SexpError, TryFromSexp};
25962597// Strict validation: confirm description == expected_desc and class == "terminal".
2598unsafe fn validate_terminal(sexp: SEXP, expected_desc: &str) -> Result<(), SexpError> {
2599let ConnState {
2600 description: desc,
2601 class: cls,
2602 ..
2603 } = unsafe { access::state(sexp) };
2604if desc != expected_desc {
2605return Err(SexpError::InvalidValue(format!(
2606"expected terminal connection with description {:?}, got {:?}",
2607 expected_desc, desc
2608 )));
2609 }
2610if cls != "terminal" {
2611return Err(SexpError::InvalidValue(format!(
2612"expected class \"terminal\", got {:?}",
2613 cls
2614 )));
2615 }
2616Ok(())
2617 }
26182619impl TryFromSexp for RStdin {
2620type Error = SexpError;
26212622fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2623unsafe { validate_terminal(sexp, "stdin") }?;
2624Ok(RStdin)
2625 }
26262627unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2628Self::try_from_sexp(sexp)
2629 }
2630 }
26312632impl TryFromSexp for RStdout {
2633type Error = SexpError;
26342635fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2636unsafe { validate_terminal(sexp, "stdout") }?;
2637Ok(RStdout)
2638 }
26392640unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2641Self::try_from_sexp(sexp)
2642 }
2643 }
26442645impl TryFromSexp for RStderr {
2646type Error = SexpError;
26472648fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2649unsafe { validate_terminal(sexp, "stderr") }?;
2650Ok(RStderr)
2651 }
26522653unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2654Self::try_from_sexp(sexp)
2655 }
2656 }
26572658/// 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.
2665impl TryFromSexp for RNullConnection {
2666type Error = SexpError;
26672668fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2669let state = unsafe { access::state(sexp) };
2670if !state.is_open {
2671return Err(SexpError::InvalidValue(
2672"expected an open connection".to_string(),
2673 ));
2674 }
2675if !state.can_write {
2676return 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.
2681unsafe { crate::sys::R_PreserveObject(sexp) };
2682Ok(unsafe { RNullConnection::from_preserved_sexp(sexp) })
2683 }
26842685unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2686Self::try_from_sexp(sexp)
2687 }
2688 }
2689}
26902691// endregion
26922693// region: txtProgressBar — TryFromSexp (issue #177)
26942695#[cfg(feature = "connections")]
2696mod txt_progress_bar_from_r {
2697use crate::from_r::{SexpError, TryFromSexp};
2698use crate::sys::R_PreserveObject;
2699use crate::txt_progress_bar::RTxtProgressBar;
2700use crate::{SEXP, SexpExt};
27012702/// The handle borrows the bar: the R caller owns it, and dropping the
2703 /// handle leaves it open.
2704impl TryFromSexp for RTxtProgressBar {
2705type Error = SexpError;
27062707fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
2708// Must be a list (VECSXP) with class "txtProgressBar".
2709if !sexp.inherits_class(c"txtProgressBar") {
2710return 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.
2715unsafe { R_PreserveObject(sexp) };
2716Ok(unsafe { RTxtProgressBar::from_preserved_sexp(sexp) })
2717 }
27182719unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
2720Self::try_from_sexp(sexp)
2721 }
2722 }
2723}
27242725// endregion
27262727// region: Helper macros for feature-gated modules
27282729/// 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) => {
2737impl $crate::from_r::TryFromSexp for Option<$t> {
2738const NATIVE_BORROW: Option<$crate::from_r::NativeBorrow> =
2739 <$t as $crate::from_r::TryFromSexp>::NATIVE_BORROW;
2740const CHARACTER_ONLY: bool = <$t as $crate::from_r::TryFromSexp>::CHARACTER_ONLY;
27412742type Error = $crate::from_r::SexpError;
2743// `NULL` (`None`) is "not given" and passes `no_na`.
2744#[inline]
2745fn __mx_has_na(&self) -> bool {
2746self.as_ref()
2747 .is_some_and(<$t as $crate::from_r::TryFromSexp>::__mx_has_na)
2748 }
27492750fn try_from_sexp(sexp: $crate::SEXP) -> Result<Self, Self::Error> {
2751use $crate::{SEXPTYPE, SexpExt};
2752if sexp.type_of() == SEXPTYPE::NILSXP {
2753return Ok(None);
2754 }
2755 <$t as $crate::from_r::TryFromSexp>::try_from_sexp(sexp).map(Some)
2756 }
27572758unsafe fn try_from_sexp_unchecked(sexp: $crate::SEXP) -> Result<Self, Self::Error> {
2759use $crate::{SEXPTYPE, SexpExt};
2760if sexp.type_of() == SEXPTYPE::NILSXP {
2761return Ok(None);
2762 }
2763unsafe {
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;
27712772/// 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) => {
2779impl $crate::from_r::TryFromSexp for Vec<$t> {
2780const NATIVE_BORROW: Option<$crate::from_r::NativeBorrow> =
2781$crate::from_r::NativeBorrow::in_list(
2782 <$t as $crate::from_r::TryFromSexp>::NATIVE_BORROW,
2783 );
27842785type Error = $crate::from_r::SexpError;
27862787fn try_from_sexp(sexp: $crate::SEXP) -> Result<Self, Self::Error> {
2788use $crate::from_r::SexpTypeError;
2789use $crate::{SEXPTYPE, SexpExt};
27902791let actual = sexp.type_of();
2792if actual != SEXPTYPE::VECSXP {
2793return Err(SexpTypeError {
2794 expected: SEXPTYPE::VECSXP,
2795 actual,
2796 }
2797 .into());
2798 }
27992800let len = sexp.len();
2801let mut result = Vec::with_capacity(len);
2802for i in 0..len {
2803let elem = sexp.vector_elt(i as $crate::R_xlen_t);
2804 result.push(<$t as $crate::from_r::TryFromSexp>::try_from_sexp(elem)?);
2805 }
2806Ok(result)
2807 }
28082809unsafe fn try_from_sexp_unchecked(sexp: $crate::SEXP) -> Result<Self, Self::Error> {
2810use $crate::from_r::SexpTypeError;
2811use $crate::{SEXPTYPE, SexpExt};
28122813let actual = sexp.type_of();
2814if actual != SEXPTYPE::VECSXP {
2815return Err(SexpTypeError {
2816 expected: SEXPTYPE::VECSXP,
2817 actual,
2818 }
2819 .into());
2820 }
28212822let len = unsafe { sexp.len_unchecked() };
2823let mut result = Vec::with_capacity(len);
2824for i in 0..len {
2825let 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 }
2830Ok(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;
28382839/// 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) => {
2846impl $crate::from_r::TryFromSexp for Vec<Option<$t>> {
2847const NATIVE_BORROW: Option<$crate::from_r::NativeBorrow> =
2848$crate::from_r::NativeBorrow::in_list(
2849 <$t as $crate::from_r::TryFromSexp>::NATIVE_BORROW,
2850 );
28512852type Error = $crate::from_r::SexpError;
28532854fn try_from_sexp(sexp: $crate::SEXP) -> Result<Self, Self::Error> {
2855use $crate::from_r::SexpTypeError;
2856use $crate::{SEXPTYPE, SexpExt};
28572858let actual = sexp.type_of();
2859if actual != SEXPTYPE::VECSXP {
2860return Err(SexpTypeError {
2861 expected: SEXPTYPE::VECSXP,
2862 actual,
2863 }
2864 .into());
2865 }
28662867let len = sexp.len();
2868let mut result = Vec::with_capacity(len);
2869for i in 0..len {
2870let elem = sexp.vector_elt(i as $crate::R_xlen_t);
2871if 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 }
2879Ok(result)
2880 }
28812882unsafe fn try_from_sexp_unchecked(sexp: $crate::SEXP) -> Result<Self, Self::Error> {
2883use $crate::from_r::SexpTypeError;
2884use $crate::{SEXPTYPE, SexpExt};
28852886let actual = sexp.type_of();
2887if actual != SEXPTYPE::VECSXP {
2888return Err(SexpTypeError {
2889 expected: SEXPTYPE::VECSXP,
2890 actual,
2891 }
2892 .into());
2893 }
28942895let len = unsafe { sexp.len_unchecked() };
2896let mut result = Vec::with_capacity(len);
2897for i in 0..len {
2898let elem = unsafe { sexp.vector_elt_unchecked(i as $crate::R_xlen_t) };
2899if 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 }
2907Ok(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;
29152916/// 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;
29232924/// 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.
2959listed: Vec<(usize, String)>,
2960 total: usize,
2961}
29622963impl 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]
2969pub fn push(&mut self, index: usize, item: impl FnOnce() -> String) {
2970if self.listed.len() < BATCHED_ERROR_CAP {
2971self.listed.push((index, item()));
2972 }
2973self.total += 1;
2974 }
29752976/// Whether any failure has been recorded.
2977#[inline]
2978pub fn is_empty(&self) -> bool {
2979self.total == 0
2980}
29812982/// Append the `"; and N more"` tail when failures past the cap were counted.
2983fn write_more_tail(&self, msg: &mut String) {
2984if self.total > self.listed.len() {
2985use std::fmt::Write;
2986let _ = msg.write_fmt(format_args!("; and {0} more", self.total - self.listed.len()))write!(msg, "; and {} more", self.total - self.listed.len());
2987 }
2988 }
29892990/// Fold the recorded `(index, reason)` failures into one
2991 /// [`SexpError::InvalidValue`]; see [`element_message`](Self::element_message).
2992pub fn into_element_error(self) -> SexpError {
2993 SexpError::InvalidValue(self.element_message())
2994 }
29952996/// 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)`
3001pub fn element_message(&self) -> String {
3002if true {
if !(self.total > 0) {
{
::core::panicking::panic_fmt(format_args!("batching zero conversion errors"));
}
};
};debug_assert!(self.total > 0, "batching zero conversion errors");
3003let mut groups: Vec<(&str, Vec<usize>)> = Vec::new();
3004for (index, reason) in &self.listed {
3005match groups.iter_mut().find(|(r, _)| *r == reason.as_str()) {
3006Some((_, indices)) => indices.push(*index),
3007None => 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 }
3010let mut msg = String::new();
3011for (k, (reason, indices)) in groups.iter().enumerate() {
3012if k > 0 {
3013 msg.push_str("; ");
3014 }
3015 msg.push_str(reason);
3016 push_element_positions(&mut msg, indices.iter().copied());
3017 }
3018self.write_more_tail(&mut msg);
3019msg3020 }
30213022/// 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).
3030pub fn into_value_error(self, what: &str) -> SexpError {
3031 SexpError::InvalidValue(self.value_message(what))
3032 }
30333034/// The message behind [`into_value_error`](Self::into_value_error).
3035fn value_message(&self, what: &str) -> String {
3036use std::fmt::Write;
3037if true {
if !(self.total > 0) {
{
::core::panicking::panic_fmt(format_args!("batching zero rejected values"));
}
};
};debug_assert!(self.total > 0, "batching zero rejected values");
3038let mut msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0} value(s): ", what))
})format!("{what} value(s): ");
3039for (k, (_, value)) in self.listed.iter().enumerate() {
3040if k > 0 {
3041 msg.push_str(", ");
3042 }
3043let _ = msg.write_fmt(format_args!("{0:?}", value))write!(msg, "{value:?}");
3044 }
3045push_element_positions(&mut msg, self.listed.iter().map(|(index, _)| *index));
3046self.write_more_tail(&mut msg);
3047msg3048 }
3049}
30503051/// 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) {
3058use std::fmt::Write;
3059msg.push_str(if indices.len() == 1 {
3060" (element "
3061} else {
3062" (elements "
3063});
3064for (k, index) in indices.enumerate() {
3065if k > 0 {
3066 msg.push_str(", ");
3067 }
3068let _ = msg.write_fmt(format_args!("{0}", index + 1))write!(msg, "{}", index + 1);
3069 }
3070msg.push(')');
3071}
30723073/// 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}
30973098impl ElementErrors {
3099/// Record the failure of the element at 0-based `index`.
3100pub(crate) fn push(&mut self, index: usize, error: SexpError) {
3101for refusal in error.reported_conditions() {
3102self.refused = true;
3103for class in refusal.classes() {
3104if !self.class.contains(class) {
3105self.class.push(class.clone());
3106 }
3107 }
3108for (name, value) in refusal.fields() {
3109if !self.data.iter().any(|(seen, _)| seen == name) {
3110self.data.push((name.clone(), value.clone()));
3111 }
3112 }
3113 }
3114self.batch.push(index, || match &error {
3115 SexpError::Condition(refusal) => refusal3116 .argument_message_str()
3117 .unwrap_or(refusal.message_str())
3118 .to_string(),
3119 other => other.r_reason(false),
3120 });
3121 }
31223123/// The values read, or the one error for every failure recorded.
3124pub(crate) fn into_result<T>(self, values: T) -> Result<T, SexpError> {
3125if self.batch.is_empty() {
3126return Ok(values);
3127 }
3128if !self.refused {
3129return Err(self.batch.into_element_error());
3130 }
3131let mut error =
3132crate::condition::RError::new(self.batch.element_message()).class(self.class);
3133for (name, value) in self.data {
3134 error = error.data(name, value);
3135 }
3136Err(SexpError::Condition(error))
3137 }
3138}
31393140/// 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,
3146mut read: impl FnMut(SEXP) -> Result<U, SexpError>,
3147) -> Result<Vec<U>, SexpError> {
3148let actual = sexp.type_of();
3149if actual != SEXPTYPE::VECSXP {
3150return Err(SexpTypeError {
3151 expected: SEXPTYPE::VECSXP,
3152actual,
3153 }
3154 .into());
3155 }
3156let len = sexp.len();
3157let mut values = Vec::with_capacity(len);
3158let mut errors = ElementErrors::default();
3159for i in 0..len {
3160let index = crate::R_xlen_t::try_from(i).expect("a list index fits R_xlen_t");
3161match read(sexp.vector_elt(index)) {
3162Ok(value) => values.push(value),
3163Err(e) => errors.push(i, e),
3164 }
3165 }
3166errors.into_result(values)
3167}
31683169/// ` (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 {
3172let mut s = String::new();
3173push_element_positions(&mut s, std::iter::once(index));
3174s3175}
31763177// endregion
31783179#[cfg(test)]
3180mod tests {
3181use super::*;
31823183#[test]
3184fn real_na_follows_r_is_na() {
3185assert!(NA_REAL.is_na());
3186// `NA_real_ * 1` in R: the same payload with the quiet bit set.
3187assert!(f64::from_bits(0x7FF8_0000_0000_07A2).is_na());
3188// `-NA_real_`: the sign bit flips, R still reads NA.
3189assert!(f64::from_bits(0xFFF0_0000_0000_07A2).is_na());
3190assert!(!f64::NAN.is_na());
3191assert!(!1954.0f64.is_na());
3192assert!(!f64::from_bits(0x0000_0000_0000_07A2).is_na());
3193 }
31943195#[test]
3196fn batched_value_error_lists_values_and_one_based_elements() {
3197let mut errors = BatchedErrors::default();
3198 errors.push(1, || "n/a".to_string());
3199 errors.push(4, || "<0.1".to_string());
3200assert_eq!(
3201 errors.value_message("non-numeric"),
3202r#"non-numeric value(s): "n/a", "<0.1" (elements 2, 5)"#
3203);
32043205let mut one = BatchedErrors::default();
3206 one.push(0, || "a\"b\u{a0}".to_string());
3207assert_eq!(
3208 one.value_message("non-numeric"),
3209r#"non-numeric value(s): "a\"b\u{a0}" (element 1)"#
3210);
3211 }
32123213#[test]
3214fn batched_value_error_caps_the_listing() {
3215let mut errors = BatchedErrors::default();
3216for i in 0..12 {
3217 errors.push(i, || format!("x{i}"));
3218 }
3219let msg = errors.value_message("non-numeric");
3220assert!(
3221 msg.starts_with(r#"non-numeric value(s): "x0", "x1","#),
3222"{msg}"
3223);
3224assert!(
3225 msg.ends_with(r#""x9" (elements 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); and 2 more"#),
3226"{msg}"
3227);
3228assert!(!msg.contains("x10"), "{msg}");
3229 }
32303231#[test]
3232fn batched_element_error_groups_reasons_with_one_based_positions() {
3233let 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());
3237assert_eq!(
3238 errors.element_message(),
3239"NA is not allowed (elements 2, 5); value out of range (element 3)"
3240);
32413242let mut one = BatchedErrors::default();
3243 one.push(0, || "precision loss".to_string());
3244let SexpError::InvalidValue(msg) = one.into_element_error() else {
3245unreachable!()
3246 };
3247assert_eq!(msg, "precision loss (element 1)");
3248 }
32493250#[test]
3251fn batched_element_error_caps_the_listing() {
3252let mut errors = BatchedErrors::default();
3253for i in 0..13 {
3254 errors.push(i, || "NA is not allowed".to_string());
3255 }
3256assert_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 }
32613262/// `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`.
3267mod str_parse_enum_with_assoc_item_variants {
3268#[derive(Debug)]
3269 #[allow(clippy::upper_case_acronyms)]
3270pub(super) enum Level {
3271 Error,
3272Err,
3273 LABEL,
3274 Inner,
3275 Value,
3276 }
32773278impl std::str::FromStr for Level {
3279type Err = String;
32803281fn from_str(s: &str) -> Result<Self, String> {
3282match 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 }
32923293crate::try_from_sexp_via_str_parse!(Level, "level", |s| s.parse::<Level>());
32943295/// The container shapes come from the `TryFromSexpElement` blankets,
3296 /// and every shape reads only character input.
3297#[test]
3298fn containers_convert() {
3299use crate::from_r::TryFromSexp;
3300const {
3301assert!(<Level as TryFromSexp>::CHARACTER_ONLY);
3302assert!(<Option<Level> as TryFromSexp>::CHARACTER_ONLY);
3303assert!(<Vec<Level> as TryFromSexp>::CHARACTER_ONLY);
3304assert!(<Vec<Option<Level>> as TryFromSexp>::CHARACTER_ONLY);
3305 }
3306 }
3307 }
3308}