Skip to main content

miniextendr_api/
rvalue.rs

1//! `RValue` — an owned, `Send`, R-native value tree.
2//!
3//! The `serde_json::Value` analogue for R's base data types: an owned (no live
4//! `SEXP`), inspectable (`Debug + Clone`), `Send` (crosses the worker boundary)
5//! representation of "an arbitrary R native value". Use it wherever you need to
6//! carry a dynamic/heterogeneous R value through Rust — e.g. condition `data =`
7//! payloads, which travel through `panic_any` and may be raised on the worker
8//! thread where a live `SEXP` would be illegal.
9//!
10//! Tuples with 1–12 fields convert into unnamed [`RValue::List`] entries,
11//! recursively using each field's `Into<RValue>` implementation. This lets
12//! condition data carry a tuple without allocating in R or requiring `Debug`.
13//!
14//! The variants enumerate R's (finite, closed) data type system — vectors only,
15//! since R has no true scalars. NA is modelled with `Option` where R carries it
16//! out of band (logical/integer/character); `Double` carries NA in the `NA_REAL`
17//! bit pattern inside `Vec<f64>` (no `Option`), matching R's in-band convention.
18//!
19//! Out of scope (existing homes): closures/environments/symbols/language objects
20//! (not data), S4 objects, `EXTPTRSXP` (use [`crate::externalptr::ExternalPtr`]),
21//! ALTREP internals. Attribute-carrying values (factor, `Date`, `POSIXct`) are
22//! modelled as their plain `Integer`/`Double` storage in v1 — richer handling is
23//! deferred until a consumer needs it.
24
25use crate::SexpExt;
26use crate::from_r::{SexpError, SexpLengthError, SexpNaError, SexpTypeError, TryFromSexp};
27use crate::into_r::IntoR;
28use crate::sexp_types::{Rcomplex, SEXPTYPE};
29use crate::{IsNA, SEXP};
30
31/// An owned, `Send`, R-native value tree. See the [module docs](self).
32#[derive(#[automatically_derived]
impl ::core::fmt::Debug for RValue {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Self::Null => ::core::fmt::Formatter::write_str(f, "Null"),
            Self::Logical(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Logical", &__self_0),
            Self::Integer(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Integer", &__self_0),
            Self::Double(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Double",
                    &__self_0),
            Self::Complex(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Complex", &__self_0),
            Self::Character(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Character", &__self_0),
            Self::Raw(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Raw",
                    &__self_0),
            Self::List(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "List",
                    &__self_0),
        }
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for RValue {
    #[inline]
    fn clone(&self) -> Self {
        match self {
            Self::Null => RValue::Null,
            Self::Logical(__self_0) =>
                RValue::Logical(::core::clone::Clone::clone(__self_0)),
            Self::Integer(__self_0) =>
                RValue::Integer(::core::clone::Clone::clone(__self_0)),
            Self::Double(__self_0) =>
                RValue::Double(::core::clone::Clone::clone(__self_0)),
            Self::Complex(__self_0) =>
                RValue::Complex(::core::clone::Clone::clone(__self_0)),
            Self::Character(__self_0) =>
                RValue::Character(::core::clone::Clone::clone(__self_0)),
            Self::Raw(__self_0) =>
                RValue::Raw(::core::clone::Clone::clone(__self_0)),
            Self::List(__self_0) =>
                RValue::List(::core::clone::Clone::clone(__self_0)),
        }
    }
}Clone)]
33pub enum RValue {
34    /// `NULL` (`NILSXP`).
35    Null,
36    /// Logical vector (`LGLSXP`). `None` is `NA`.
37    Logical(Vec<Option<bool>>),
38    /// Integer vector (`INTSXP`). `None` is `NA`.
39    Integer(Vec<Option<i32>>),
40    /// Double vector (`REALSXP`). NA is carried in the `NA_REAL` bit pattern.
41    Double(Vec<f64>),
42    /// Complex vector (`CPLXSXP`).
43    Complex(Vec<Rcomplex>),
44    /// Character vector (`STRSXP`). `None` is `NA`.
45    Character(Vec<Option<String>>),
46    /// Raw byte vector (`RAWSXP`). No NA.
47    Raw(Vec<u8>),
48    /// Generic list (`VECSXP`). Recursive; a `None` name is an unnamed slot.
49    List(Vec<(Option<String>, RValue)>),
50}
51
52// region: IntoR
53
54impl IntoR for RValue {
55    type Error = std::convert::Infallible;
56
57    fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
58        Ok(self.into_sexp())
59    }
60
61    unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
62        self.try_into_sexp()
63    }
64
65    fn into_sexp(self) -> SEXP {
66        match self {
67            RValue::Null => SEXP::nil(),
68            RValue::Logical(v) => v.into_sexp(),
69            RValue::Integer(v) => v.into_sexp(),
70            RValue::Double(v) => v.into_sexp(),
71            RValue::Complex(v) => v.into_sexp(),
72            RValue::Character(v) => v.into_sexp(),
73            RValue::Raw(v) => v.into_sexp(),
74            RValue::List(pairs) => list_into_sexp(pairs),
75        }
76    }
77}
78
79/// Build a `VECSXP` from list pairs, recursing into each child.
80///
81/// Each child `SEXP` is `protect_raw`-rooted before the next child allocates —
82/// the same GC discipline as `AsNamedList::into_sexp` (#1030/#1045), required
83/// under `gctorture`. The scope drops once the list is built.
84fn list_into_sexp(pairs: Vec<(Option<String>, RValue)>) -> SEXP {
85    use crate::list::List;
86    // SAFETY: `IntoR::into_sexp` for `#[miniextendr]` return values runs on the R
87    // main thread.
88    unsafe {
89        let scope = crate::gc_protect::ProtectScope::new();
90        if pairs.iter().any(|(name, _)| name.is_some()) {
91            // Named (R fills unnamed slots with ""). `from_raw_pairs` sets names.
92            let named: Vec<(String, SEXP)> = pairs
93                .into_iter()
94                .map(|(name, val)| (name.unwrap_or_default(), scope.protect_raw(val.into_sexp())))
95                .collect();
96            List::from_raw_pairs(named).into_sexp()
97        } else {
98            let values: Vec<SEXP> = pairs
99                .into_iter()
100                .map(|(_, val)| scope.protect_raw(val.into_sexp()))
101                .collect();
102            List::from_raw_values(values).into_sexp()
103        }
104    }
105}
106
107// endregion
108
109// region: TryFromSexp
110
111impl TryFromSexp for RValue {
112    type Error = SexpError;
113
114    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
115        let n = sexp.len();
116        match sexp.type_of() {
117            SEXPTYPE::NILSXP => Ok(RValue::Null),
118            SEXPTYPE::LGLSXP => Ok(RValue::Logical(
119                (0..n as isize)
120                    .map(|i| {
121                        let x = sexp.logical_elt(i);
122                        (!x.is_na()).then_some(x != 0)
123                    })
124                    .collect(),
125            )),
126            SEXPTYPE::INTSXP => Ok(RValue::Integer(
127                (0..n as isize)
128                    .map(|i| {
129                        let x = sexp.integer_elt(i);
130                        (!x.is_na()).then_some(x)
131                    })
132                    .collect(),
133            )),
134            // NA stays in the bit pattern — no Option needed (decision 2).
135            SEXPTYPE::REALSXP => Ok(RValue::Double(
136                (0..n as isize).map(|i| sexp.real_elt(i)).collect(),
137            )),
138            SEXPTYPE::CPLXSXP => Ok(RValue::Complex(
139                (0..n as isize).map(|i| sexp.complex_elt(i)).collect(),
140            )),
141            SEXPTYPE::STRSXP => Ok(RValue::Character(
142                (0..n as isize)
143                    .map(|i| {
144                        if sexp.string_elt(i).is_na_string() {
145                            None
146                        } else {
147                            sexp.string_elt_str(i).map(str::to_string)
148                        }
149                    })
150                    .collect(),
151            )),
152            // SAFETY: type checked to be RAWSXP; `as_slice` handles the empty (0x1) case.
153            SEXPTYPE::RAWSXP => Ok(RValue::Raw(unsafe { sexp.as_slice::<u8>() }.to_vec())),
154            SEXPTYPE::VECSXP => {
155                let names = sexp.get_names();
156                let mut pairs = Vec::with_capacity(n);
157                for i in 0..n as isize {
158                    // `||` short-circuits, so `string_elt` is only reached for a
159                    // character names vector. NA / empty names are unnamed slots.
160                    let name = if names.is_nil()
161                        || !names.is_character()
162                        || names.string_elt(i).is_na_string()
163                    {
164                        None
165                    } else {
166                        names
167                            .string_elt_str(i)
168                            .filter(|s| !s.is_empty())
169                            .map(str::to_string)
170                    };
171                    pairs.push((name, RValue::try_from_sexp(sexp.vector_elt(i))?));
172                }
173                Ok(RValue::List(pairs))
174            }
175            other => Err(SexpError::InvalidValue(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("RValue cannot represent SEXP of type {0:?} (only R data vectors are modelled)",
                other))
    })format!(
176                "RValue cannot represent SEXP of type {other:?} (only R data vectors are modelled)"
177            ))),
178        }
179    }
180}
181
182// endregion
183
184// region: ergonomic From impls (scalars wrap to length-1 vectors, per R semantics)
185
186impl From<i32> for RValue {
187    fn from(v: i32) -> Self {
188        RValue::Integer(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Some(v)]))vec![Some(v)])
189    }
190}
191impl From<f64> for RValue {
192    fn from(v: f64) -> Self {
193        RValue::Double(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [v]))vec![v])
194    }
195}
196impl From<bool> for RValue {
197    fn from(v: bool) -> Self {
198        RValue::Logical(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Some(v)]))vec![Some(v)])
199    }
200}
201impl From<String> for RValue {
202    fn from(v: String) -> Self {
203        RValue::Character(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Some(v)]))vec![Some(v)])
204    }
205}
206impl From<&str> for RValue {
207    fn from(v: &str) -> Self {
208        RValue::Character(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Some(v.to_string())]))vec![Some(v.to_string())])
209    }
210}
211impl From<Vec<i32>> for RValue {
212    fn from(v: Vec<i32>) -> Self {
213        RValue::Integer(v.into_iter().map(Some).collect())
214    }
215}
216impl From<Vec<f64>> for RValue {
217    fn from(v: Vec<f64>) -> Self {
218        RValue::Double(v)
219    }
220}
221impl From<Vec<bool>> for RValue {
222    fn from(v: Vec<bool>) -> Self {
223        RValue::Logical(v.into_iter().map(Some).collect())
224    }
225}
226impl From<Vec<String>> for RValue {
227    fn from(v: Vec<String>) -> Self {
228        RValue::Character(v.into_iter().map(Some).collect())
229    }
230}
231impl From<Vec<&str>> for RValue {
232    fn from(v: Vec<&str>) -> Self {
233        RValue::Character(v.into_iter().map(|s| Some(s.to_string())).collect())
234    }
235}
236
237// endregion
238
239// region: Tuples (owned positional lists, no R allocation)
240
241macro_rules! impl_tuple_rvalue {
242    ($n:literal, $name:literal; $($T:ident $R:ident $idx:tt),+) => {
243        impl<$($T: Into<RValue>),+> From<($($T,)+)> for RValue {
244            fn from(value: ($($T,)+)) -> Self {
245                Self::List(vec![$((None, value.$idx.into()),)+])
246            }
247        }
248    };
249}
250
251impl<A: Into<RValue>, B: Into<RValue>, C: Into<RValue>, D: Into<RValue>,
    E: Into<RValue>, F: Into<RValue>, G: Into<RValue>, H: Into<RValue>,
    I: Into<RValue>, J: Into<RValue>, K: Into<RValue>, L: Into<RValue>>
    From<(A, B, C, D, E, F, G, H, I, J, K, L)> for RValue {
    fn from(value: (A, B, C, D, E, F, G, H, I, J, K, L)) -> Self {
        Self::List(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                    [(None, value.0.into()), (None, value.1.into()),
                            (None, value.2.into()), (None, value.3.into()),
                            (None, value.4.into()), (None, value.5.into()),
                            (None, value.6.into()), (None, value.7.into()),
                            (None, value.8.into()), (None, value.9.into()),
                            (None, value.10.into()), (None, value.11.into())])))
    }
}crate::tuple_macros::for_each_tuple!(impl_tuple_rvalue);
252
253// endregion
254
255// region: NA-aware Option / Vec<Option> + wide-integer ladder (ported from #1044/#995)
256
257// `Option<T>` → length-1 vector, `None` → `NA`. Integer/logical/character carry
258// NA out of band via the `None` slot; `Double` carries it in the `NA_REAL` bit
259// pattern (no `Option<f64>` variant — see the module docs).
260
261impl From<Option<i32>> for RValue {
262    fn from(v: Option<i32>) -> Self {
263        RValue::Integer(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [v]))vec![v])
264    }
265}
266impl From<Option<f64>> for RValue {
267    fn from(v: Option<f64>) -> Self {
268        RValue::Double(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [v.unwrap_or(crate::altrep_traits::NA_REAL)]))vec![v.unwrap_or(crate::altrep_traits::NA_REAL)])
269    }
270}
271impl From<Option<bool>> for RValue {
272    fn from(v: Option<bool>) -> Self {
273        RValue::Logical(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [v]))vec![v])
274    }
275}
276impl From<Option<String>> for RValue {
277    fn from(v: Option<String>) -> Self {
278        RValue::Character(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [v]))vec![v])
279    }
280}
281impl From<Option<&str>> for RValue {
282    fn from(v: Option<&str>) -> Self {
283        RValue::Character(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [v.map(str::to_string)]))vec![v.map(str::to_string)])
284    }
285}
286impl From<Vec<Option<i32>>> for RValue {
287    fn from(v: Vec<Option<i32>>) -> Self {
288        RValue::Integer(v)
289    }
290}
291impl From<Vec<Option<f64>>> for RValue {
292    fn from(v: Vec<Option<f64>>) -> Self {
293        RValue::Double(
294            v.into_iter()
295                .map(|x| x.unwrap_or(crate::altrep_traits::NA_REAL))
296                .collect(),
297        )
298    }
299}
300impl From<Vec<Option<bool>>> for RValue {
301    fn from(v: Vec<Option<bool>>) -> Self {
302        RValue::Logical(v)
303    }
304}
305impl From<Vec<Option<String>>> for RValue {
306    fn from(v: Vec<Option<String>>) -> Self {
307        RValue::Character(v)
308    }
309}
310impl From<Vec<Option<&str>>> for RValue {
311    fn from(v: Vec<Option<&str>>) -> Self {
312        RValue::Character(v.into_iter().map(|s| s.map(str::to_string)).collect())
313    }
314}
315
316/// Wide-integer ladder: an `i64` (or `u32`) that fits in `i32` **and** is not
317/// `i32::MIN` (R's `NA_integer_`) becomes an `Integer`; anything else becomes a
318/// `Double`. Mirrors R's own integer→double promotion for out-of-range values.
319impl From<i64> for RValue {
320    fn from(v: i64) -> Self {
321        match i32::try_from(v) {
322            Ok(n) if !n.is_na() => RValue::Integer(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Some(n)]))vec![Some(n)]),
323            // Out of `i32` range (or exactly `NA_integer_`) — promote to double.
324            // `i64 → f64` is lossy past 2^53, matching R's own integer overflow.
325            _ => RValue::Double(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [v as f64]))vec![v as f64]),
326        }
327    }
328}
329impl From<u32> for RValue {
330    /// Lossless widening to `i64`, then the shared ladder. A `u32` is never
331    /// `i32::MIN` as an `i32`, so only the `> i32::MAX` case falls to `Double`.
332    fn from(v: u32) -> Self {
333        RValue::from(i64::from(v))
334    }
335}
336
337// endregion
338
339// region: inspection accessors + owned extraction (#1050 decision 4, on demand)
340
341impl RValue {
342    /// The `SEXPTYPE` this value materialises to via [`IntoR`].
343    pub fn sexptype(&self) -> SEXPTYPE {
344        match self {
345            RValue::Null => SEXPTYPE::NILSXP,
346            RValue::Logical(_) => SEXPTYPE::LGLSXP,
347            RValue::Integer(_) => SEXPTYPE::INTSXP,
348            RValue::Double(_) => SEXPTYPE::REALSXP,
349            RValue::Complex(_) => SEXPTYPE::CPLXSXP,
350            RValue::Character(_) => SEXPTYPE::STRSXP,
351            RValue::Raw(_) => SEXPTYPE::RAWSXP,
352            RValue::List(_) => SEXPTYPE::VECSXP,
353        }
354    }
355
356    /// `true` if this is [`RValue::Null`].
357    pub fn is_null(&self) -> bool {
358        #[allow(non_exhaustive_omitted_patterns)] match self {
    RValue::Null => true,
    _ => false,
}matches!(self, RValue::Null)
359    }
360
361    /// Wrap any `T: Debug` as a length-1 `Character` carrying its `{:?}`
362    /// rendering — the escape hatch for a value with no R-native mapping
363    /// (e.g. a Rust range). The rendering happens eagerly here, so nothing
364    /// borrows across the `Send` boundary a condition payload crosses.
365    ///
366    /// ```ignore
367    /// # use miniextendr_api::RValue;
368    /// assert_eq!(RValue::debug(0..=100).as_str(), Some("0..=100"));
369    /// ```
370    pub fn debug<T: std::fmt::Debug>(value: T) -> Self {
371        RValue::Character(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Some(::alloc::__export::must_use({
                            ::alloc::fmt::format(format_args!("{0:?}", value))
                        }))]))vec![Some(format!("{value:?}"))])
372    }
373
374    /// The single non-NA `i32` of a length-1 `Integer`, else `None`.
375    ///
376    /// Returns `None` for the wrong variant, a length other than 1, or NA —
377    /// the borrow-friendly counterpart to `i32::try_from` when you don't need
378    /// to know *why* it failed.
379    pub fn as_i32(&self) -> Option<i32> {
380        match self {
381            RValue::Integer(v) if v.len() == 1 => v[0],
382            _ => None,
383        }
384    }
385
386    /// The single non-NA `f64` of a length-1 `Double`, else `None`.
387    ///
388    /// R's NA yields `None`, matching [`as_i32`](Self::as_i32). NA is decided by
389    /// R's `R_IsNA` rule, so a computed NA (`NA_real_ * 1`) counts; any other NaN
390    /// is returned as a value.
391    pub fn as_f64(&self) -> Option<f64> {
392        match self {
393            RValue::Double(v) if v.len() == 1 => {
394                let x = v[0];
395                (!x.is_na()).then_some(x)
396            }
397            _ => None,
398        }
399    }
400
401    /// The single non-NA `bool` of a length-1 `Logical`, else `None`.
402    pub fn as_bool(&self) -> Option<bool> {
403        match self {
404            RValue::Logical(v) if v.len() == 1 => v[0],
405            _ => None,
406        }
407    }
408
409    /// The single non-NA string of a length-1 `Character`, else `None`.
410    pub fn as_str(&self) -> Option<&str> {
411        match self {
412            RValue::Character(v) if v.len() == 1 => v[0].as_deref(),
413            _ => None,
414        }
415    }
416}
417
418/// `Type` error for a variant mismatch: the value's `SEXPTYPE` vs the expected one.
419fn wrong_type(expected: SEXPTYPE, actual: &RValue) -> SexpError {
420    SexpError::Type(SexpTypeError {
421        expected,
422        actual: actual.sexptype(),
423    })
424}
425
426/// `TryFrom<RValue>` for a scalar pulled from a length-1 Option-backed variant.
427///
428/// Fails with `Length` for a non-scalar, `Na` for NA, `Type` for the wrong
429/// variant — the failure modes a numeric `CoerceError` can't express, which is
430/// why this is `TryFrom`/`SexpError` rather than a `coerce.rs` impl.
431macro_rules! try_from_scalar_opt {
432    ($t:ty, $variant:ident, $ty:expr) => {
433        impl TryFrom<RValue> for $t {
434            type Error = SexpError;
435            fn try_from(value: RValue) -> Result<Self, SexpError> {
436                match value {
437                    RValue::$variant(xs) => {
438                        if xs.len() != 1 {
439                            return Err(SexpError::Length(SexpLengthError {
440                                expected: 1,
441                                actual: xs.len(),
442                            }));
443                        }
444                        xs.into_iter()
445                            .next()
446                            .unwrap()
447                            .ok_or(SexpError::Na(SexpNaError { sexp_type: $ty }))
448                    }
449                    other => Err(wrong_type($ty, &other)),
450                }
451            }
452        }
453    };
454}
455impl TryFrom<RValue> for i32 {
    type Error = SexpError;
    fn try_from(value: RValue) -> Result<Self, SexpError> {
        match value {
            RValue::Integer(xs) => {
                if xs.len() != 1 {
                    return Err(SexpError::Length(SexpLengthError {
                                    expected: 1,
                                    actual: xs.len(),
                                }));
                }
                xs.into_iter().next().unwrap().ok_or(SexpError::Na(SexpNaError {
                            sexp_type: SEXPTYPE::INTSXP,
                        }))
            }
            other => Err(wrong_type(SEXPTYPE::INTSXP, &other)),
        }
    }
}try_from_scalar_opt!(i32, Integer, SEXPTYPE::INTSXP);
456impl TryFrom<RValue> for bool {
    type Error = SexpError;
    fn try_from(value: RValue) -> Result<Self, SexpError> {
        match value {
            RValue::Logical(xs) => {
                if xs.len() != 1 {
                    return Err(SexpError::Length(SexpLengthError {
                                    expected: 1,
                                    actual: xs.len(),
                                }));
                }
                xs.into_iter().next().unwrap().ok_or(SexpError::Na(SexpNaError {
                            sexp_type: SEXPTYPE::LGLSXP,
                        }))
            }
            other => Err(wrong_type(SEXPTYPE::LGLSXP, &other)),
        }
    }
}try_from_scalar_opt!(bool, Logical, SEXPTYPE::LGLSXP);
457impl TryFrom<RValue> for String {
    type Error = SexpError;
    fn try_from(value: RValue) -> Result<Self, SexpError> {
        match value {
            RValue::Character(xs) => {
                if xs.len() != 1 {
                    return Err(SexpError::Length(SexpLengthError {
                                    expected: 1,
                                    actual: xs.len(),
                                }));
                }
                xs.into_iter().next().unwrap().ok_or(SexpError::Na(SexpNaError {
                            sexp_type: SEXPTYPE::STRSXP,
                        }))
            }
            other => Err(wrong_type(SEXPTYPE::STRSXP, &other)),
        }
    }
}try_from_scalar_opt!(String, Character, SEXPTYPE::STRSXP);
458
459// `Double` carries NA in the bit pattern, so it can't reuse the Option macro.
460impl TryFrom<RValue> for f64 {
461    type Error = SexpError;
462    fn try_from(value: RValue) -> Result<Self, SexpError> {
463        match value {
464            RValue::Double(xs) => {
465                if xs.len() != 1 {
466                    return Err(SexpError::Length(SexpLengthError {
467                        expected: 1,
468                        actual: xs.len(),
469                    }));
470                }
471                let x = xs[0];
472                // `R_IsNA`: a computed NA is an NA error too; other NaNs pass.
473                if x.is_na() {
474                    Err(SexpError::Na(SexpNaError {
475                        sexp_type: SEXPTYPE::REALSXP,
476                    }))
477                } else {
478                    Ok(x)
479                }
480            }
481            other => Err(wrong_type(SEXPTYPE::REALSXP, &other)),
482        }
483    }
484}
485
486/// `TryFrom<RValue>` for the whole vector of a single variant (NA preserved).
487macro_rules! try_from_vec {
488    ($t:ty, $variant:ident, $ty:expr) => {
489        impl TryFrom<RValue> for $t {
490            type Error = SexpError;
491            fn try_from(value: RValue) -> Result<Self, SexpError> {
492                match value {
493                    RValue::$variant(xs) => Ok(xs),
494                    other => Err(wrong_type($ty, &other)),
495                }
496            }
497        }
498    };
499}
500impl TryFrom<RValue> for Vec<Option<bool>> {
    type Error = SexpError;
    fn try_from(value: RValue) -> Result<Self, SexpError> {
        match value {
            RValue::Logical(xs) => Ok(xs),
            other => Err(wrong_type(SEXPTYPE::LGLSXP, &other)),
        }
    }
}try_from_vec!(Vec<Option<bool>>, Logical, SEXPTYPE::LGLSXP);
501impl TryFrom<RValue> for Vec<Option<i32>> {
    type Error = SexpError;
    fn try_from(value: RValue) -> Result<Self, SexpError> {
        match value {
            RValue::Integer(xs) => Ok(xs),
            other => Err(wrong_type(SEXPTYPE::INTSXP, &other)),
        }
    }
}try_from_vec!(Vec<Option<i32>>, Integer, SEXPTYPE::INTSXP);
502impl TryFrom<RValue> for Vec<f64> {
    type Error = SexpError;
    fn try_from(value: RValue) -> Result<Self, SexpError> {
        match value {
            RValue::Double(xs) => Ok(xs),
            other => Err(wrong_type(SEXPTYPE::REALSXP, &other)),
        }
    }
}try_from_vec!(Vec<f64>, Double, SEXPTYPE::REALSXP);
503impl TryFrom<RValue> for Vec<Rcomplex> {
    type Error = SexpError;
    fn try_from(value: RValue) -> Result<Self, SexpError> {
        match value {
            RValue::Complex(xs) => Ok(xs),
            other => Err(wrong_type(SEXPTYPE::CPLXSXP, &other)),
        }
    }
}try_from_vec!(Vec<Rcomplex>, Complex, SEXPTYPE::CPLXSXP);
504impl TryFrom<RValue> for Vec<Option<String>> {
    type Error = SexpError;
    fn try_from(value: RValue) -> Result<Self, SexpError> {
        match value {
            RValue::Character(xs) => Ok(xs),
            other => Err(wrong_type(SEXPTYPE::STRSXP, &other)),
        }
    }
}try_from_vec!(Vec<Option<String>>, Character, SEXPTYPE::STRSXP);
505impl TryFrom<RValue> for Vec<u8> {
    type Error = SexpError;
    fn try_from(value: RValue) -> Result<Self, SexpError> {
        match value {
            RValue::Raw(xs) => Ok(xs),
            other => Err(wrong_type(SEXPTYPE::RAWSXP, &other)),
        }
    }
}try_from_vec!(Vec<u8>, Raw, SEXPTYPE::RAWSXP);
506impl TryFrom<RValue> for Vec<(Option<String>, RValue)> {
    type Error = SexpError;
    fn try_from(value: RValue) -> Result<Self, SexpError> {
        match value {
            RValue::List(xs) => Ok(xs),
            other => Err(wrong_type(SEXPTYPE::VECSXP, &other)),
        }
    }
}try_from_vec!(Vec<(Option<String>, RValue)>, List, SEXPTYPE::VECSXP);
507
508// endregion
509
510#[cfg(test)]
511mod tests {
512    use super::*;
513
514    // Accessors and TryFrom operate on owned `RValue` — no SEXP, no R runtime.
515
516    #[test]
517    fn as_scalar_accessors() {
518        assert_eq!(RValue::from(7i32).as_i32(), Some(7));
519        assert_eq!(RValue::from(1.5).as_f64(), Some(1.5));
520        assert_eq!(RValue::from(true).as_bool(), Some(true));
521        assert_eq!(RValue::from("hi").as_str(), Some("hi"));
522
523        // wrong variant
524        assert_eq!(RValue::from("hi").as_i32(), None);
525        // wrong length
526        assert_eq!(RValue::Integer(vec![Some(1), Some(2)]).as_i32(), None);
527        // NA
528        assert_eq!(RValue::Integer(vec![None]).as_i32(), None);
529        assert_eq!(
530            RValue::Double(vec![crate::altrep_traits::NA_REAL]).as_f64(),
531            None
532        );
533        assert!(RValue::Null.is_null());
534    }
535
536    #[test]
537    fn try_from_scalar_ok_and_errors() {
538        assert_eq!(i32::try_from(RValue::from(7i32)).unwrap(), 7);
539        assert_eq!(f64::try_from(RValue::from(2.5)).unwrap(), 2.5);
540        assert!(!bool::try_from(RValue::from(false)).unwrap());
541        assert_eq!(String::try_from(RValue::from("x")).unwrap(), "x");
542
543        // wrong variant → Type
544        assert!(matches!(
545            i32::try_from(RValue::from("x")),
546            Err(SexpError::Type(_))
547        ));
548        // wrong length → Length
549        assert!(matches!(
550            i32::try_from(RValue::Integer(vec![Some(1), Some(2)])),
551            Err(SexpError::Length(_))
552        ));
553        // NA → Na
554        assert!(matches!(
555            i32::try_from(RValue::Integer(vec![None])),
556            Err(SexpError::Na(_))
557        ));
558        assert!(matches!(
559            f64::try_from(RValue::Double(vec![crate::altrep_traits::NA_REAL])),
560            Err(SexpError::Na(_))
561        ));
562    }
563
564    /// A computed NA (`NA_real_ * 1` quiets the NaN: bits `0x7FF8_0000_0000_07A2`)
565    /// is NA by R's `R_IsNA` rule; a plain NaN is a value.
566    #[test]
567    fn double_na_follows_r_is_na() {
568        let computed = f64::from_bits(0x7FF8_0000_0000_07A2);
569        assert_eq!(RValue::Double(vec![computed]).as_f64(), None);
570        assert!(matches!(
571            f64::try_from(RValue::Double(vec![computed])),
572            Err(SexpError::Na(_))
573        ));
574        assert!(
575            RValue::Double(vec![f64::NAN])
576                .as_f64()
577                .is_some_and(f64::is_nan)
578        );
579        assert!(f64::try_from(RValue::Double(vec![f64::NAN])).is_ok_and(f64::is_nan));
580    }
581
582    #[test]
583    fn from_option_scalars() {
584        assert!(matches!(RValue::from(Some(7_i32)), RValue::Integer(v) if v == vec![Some(7)]));
585        assert!(matches!(RValue::from(None::<i32>), RValue::Integer(v) if v == vec![None]));
586        assert!(matches!(RValue::from(Some(true)), RValue::Logical(v) if v == vec![Some(true)]));
587        assert!(matches!(RValue::from(None::<bool>), RValue::Logical(v) if v == vec![None]));
588        assert!(
589            matches!(RValue::from(Some("x")), RValue::Character(v) if v == vec![Some("x".to_string())])
590        );
591        assert!(matches!(RValue::from(None::<&str>), RValue::Character(v) if v == vec![None]));
592        assert!(
593            matches!(RValue::from(Some("y".to_string())), RValue::Character(v) if v == vec![Some("y".to_string())])
594        );
595
596        // f64 has no Option variant: Some → value, None → NA_REAL bit pattern.
597        assert_eq!(RValue::from(Some(1.5_f64)).as_f64(), Some(1.5));
598        assert_eq!(RValue::from(None::<f64>).as_f64(), None);
599    }
600
601    #[test]
602    fn from_vec_option() {
603        assert!(
604            matches!(RValue::from(vec![Some(1_i32), None, Some(3)]), RValue::Integer(v) if v == vec![Some(1), None, Some(3)])
605        );
606        assert!(
607            matches!(RValue::from(vec![Some(true), None]), RValue::Logical(v) if v == vec![Some(true), None])
608        );
609        assert!(
610            matches!(RValue::from(vec![Some("a".to_string()), None]), RValue::Character(v) if v == vec![Some("a".to_string()), None])
611        );
612        assert!(
613            matches!(RValue::from(vec![Some("b"), None]), RValue::Character(v) if v == vec![Some("b".to_string()), None])
614        );
615
616        // Vec<Option<f64>> → Double with NA_REAL in the None slot.
617        let RValue::Double(v) = RValue::from(vec![Some(0.5_f64), None]) else {
618            panic!("expected Double");
619        };
620        assert_eq!(v[0], 0.5);
621        assert_eq!(v[1].to_bits(), crate::altrep_traits::NA_REAL.to_bits());
622    }
623
624    #[test]
625    fn from_wide_integer_ladder() {
626        // Fits in i32 (and not i32::MIN) → Integer.
627        assert!(matches!(RValue::from(42_i64), RValue::Integer(v) if v == vec![Some(42)]));
628        assert!(
629            matches!(RValue::from(i32::MAX as i64), RValue::Integer(v) if v == vec![Some(i32::MAX)])
630        );
631        assert!(matches!(RValue::from(7_u32), RValue::Integer(v) if v == vec![Some(7)]));
632        // i32::MIN is NA_integer_ → promote to Double rather than emit NA.
633        assert!(matches!(RValue::from(i32::MIN as i64), RValue::Double(_)));
634        // Just past i32::MAX → Double.
635        assert!(
636            matches!(RValue::from(i32::MAX as i64 + 1), RValue::Double(v) if v == vec![(i32::MAX as i64 + 1) as f64])
637        );
638        // u32::MAX exceeds i32::MAX → Double.
639        assert!(
640            matches!(RValue::from(u32::MAX), RValue::Double(v) if v == vec![f64::from(u32::MAX)])
641        );
642    }
643
644    #[test]
645    fn debug_stringifies() {
646        assert_eq!(RValue::debug(0..=100).as_str(), Some("0..=100"));
647        assert_eq!(RValue::debug(vec![1, 2]).as_str(), Some("[1, 2]"));
648    }
649
650    #[test]
651    fn try_from_whole_vector() {
652        let v: Vec<Option<i32>> = RValue::Integer(vec![Some(1), None]).try_into().unwrap();
653        assert_eq!(v, vec![Some(1), None]);
654
655        let raw: Vec<u8> = RValue::Raw(vec![1, 2, 3]).try_into().unwrap();
656        assert_eq!(raw, vec![1, 2, 3]);
657
658        // wrong variant → Type
659        let bad: Result<Vec<u8>, _> = RValue::from(1i32).try_into();
660        assert!(matches!(bad, Err(SexpError::Type(_))));
661    }
662}