extendr_api/robj/
into_robj.rs

1use super::*;
2use crate::scalar::Scalar;
3use crate::single_threaded;
4use extendr_ffi::{
5    cetype_t, R_BlankString, R_NaInt, R_NaReal, R_NaString, R_NilValue, Rcomplex, Rf_mkCharLenCE,
6    COMPLEX, INTEGER, LOGICAL, RAW, REAL, SET_STRING_ELT, SEXPTYPE,
7};
8mod repeat_into_robj;
9
10/// Returns an `CHARSXP` based on the provided `&str`.
11///
12/// Note that R does string interning, thus repeated application of this
13/// function on the same string, will incur little computational cost.
14///
15/// Note, that you must protect the return value somehow.
16pub(crate) fn str_to_character(s: &str) -> SEXP {
17    unsafe {
18        if s.is_na() {
19            R_NaString
20        } else if s.is_empty() {
21            R_BlankString
22        } else {
23            single_threaded(|| {
24                // this function embeds a terminating \nul
25                Rf_mkCharLenCE(s.as_ptr().cast(), s.len() as i32, cetype_t::CE_UTF8)
26            })
27        }
28    }
29}
30
31/// Convert a null to an Robj.
32impl From<()> for Robj {
33    fn from(_: ()) -> Self {
34        // Note: we do not need to protect this.
35        unsafe { Robj::from_sexp(R_NilValue) }
36    }
37}
38
39/// Convert a [`Result`] to an [`Robj`].
40///
41/// To use the `?`-operator, an extendr-function must return either [`extendr_api::error::Result`] or [`std::result::Result`].
42/// Use of `panic!` in extendr is discouraged due to memory leakage.
43///
44/// Alternative behaviors enabled by feature toggles:
45/// extendr-api supports different conversions from [`Result<T,E>`] into `Robj`.
46/// Below, `x_ok` represents an R variable on R side which was returned from rust via `T::into_robj()` or similar.
47/// Likewise, `x_err` was returned to R side from rust via `E::into_robj()` or similar.
48/// extendr-api
49/// * `result_list`: `Ok(T)` is encoded as `list(ok = x_ok, err = NULL)` and `Err` as `list(ok = NULL, err = e_err)`.
50/// * `result_condition'`: `Ok(T)` is encoded as `x_ok` and `Err(E)` as `condition(msg="extendr_error", value = x_err, class=c("extendr_error", "error", "condition"))`
51/// * More than one enabled feature: Only one feature gate will take effect, the current order of precedence is [`result_list`, `result_condition`, ... ].
52/// * Neither of the above (default): `Ok(T)` is encoded as `x_ok` and `Err(E)` will trigger `throw_r_error()` with the error message.
53/// ```
54/// use extendr_api::prelude::*;
55/// fn my_func() -> Result<f64> {
56///     Ok(1.0)
57/// }
58///
59/// test! {
60///     assert_eq!(r!(my_func()), r!(1.0));
61/// }
62/// ```
63///
64/// [`extendr_api::error::Result`]: crate::error::Result
65#[cfg(not(any(feature = "result_list", feature = "result_condition")))]
66impl<T, E> From<std::result::Result<T, E>> for Robj
67where
68    T: Into<Robj>,
69    E: std::fmt::Debug + std::fmt::Display,
70{
71    fn from(res: std::result::Result<T, E>) -> Self {
72        res.unwrap().into()
73    }
74}
75
76/// Convert a [`Result`] to an [`Robj`]. Return either `Ok` value or `Err` value wrapped in an
77/// error condition. This allows using `?` operator in functions
78/// and returning [`Result<T>`] without panicking on `Err`. `T` must implement [`IntoRobj`].
79///
80/// Returns `Ok` value as is. Returns `Err` wrapped in an R error condition. The `Err` is placed in
81/// $value field of the condition, and its message is set to 'extendr_err'
82#[cfg(all(feature = "result_condition", not(feature = "result_list")))]
83impl<T, E> From<std::result::Result<T, E>> for Robj
84where
85    T: Into<Robj>,
86    E: Into<Robj>,
87{
88    fn from(res: std::result::Result<T, E>) -> Self {
89        use crate as extendr_api;
90        match res {
91            Ok(x) => x.into(),
92            Err(x) => {
93                let mut err = list!(message = "extendr_err", value = x.into());
94                err.set_class(["extendr_error", "error", "condition"])
95                    .expect("internal error: failed to set class");
96                err.into()
97            }
98        }
99    }
100}
101
102/// Convert a `Result` to an R `List` with an `ok` and `err` elements.
103/// This allows using `?` operator in functions
104/// and returning [`std::result::Result`] or [`extendr_api::error::Result`]
105/// without panicking on `Err`.
106///
107/// [`extendr_api::error::Result`]: crate::error::Result
108#[cfg(feature = "result_list")]
109impl<T, E> From<std::result::Result<T, E>> for Robj
110where
111    T: Into<Robj>,
112    E: Into<Robj>,
113{
114    fn from(res: std::result::Result<T, E>) -> Self {
115        use crate as extendr_api;
116        let mut result = match res {
117            Ok(x) => list!(ok = x.into(), err = NULL),
118            Err(x) => {
119                let err_robj = x.into();
120                if err_robj.is_null() {
121                    panic!("Internal error: result_list not allowed to return NULL as err-value")
122                }
123                list!(ok = NULL, err = err_robj)
124            }
125        };
126        result
127            .set_class(&["extendr_result"])
128            .expect("Internal error: failed to set class");
129        result.into()
130    }
131}
132
133// string conversions from Error trait to Robj and String
134impl From<Error> for Robj {
135    fn from(res: Error) -> Self {
136        res.to_string().into()
137    }
138}
139impl From<Error> for String {
140    fn from(res: Error) -> Self {
141        res.to_string()
142    }
143}
144
145/// Convert an Robj reference into a borrowed Robj.
146impl From<&Robj> for Robj {
147    // Note: we should probably have a much better reference
148    // mechanism as double-free or underprotection is a distinct possibility.
149    fn from(val: &Robj) -> Self {
150        unsafe { Robj::from_sexp(val.get()) }
151    }
152}
153
154/// This is an extension trait to provide a convenience method `into_robj()`.
155///
156/// Defer to `From<T> for Robj`-impls if you have custom types.
157///
158pub trait IntoRobj {
159    fn into_robj(self) -> Robj;
160}
161
162impl<T> IntoRobj for T
163where
164    Robj: From<T>,
165{
166    fn into_robj(self) -> Robj {
167        self.into()
168    }
169}
170
171/// `ToVectorValue` is a trait that allows many different types
172/// to be converted to vectors. It is used as a type parameter
173/// to `collect_robj()`.
174pub trait ToVectorValue {
175    fn sexptype() -> SEXPTYPE {
176        SEXPTYPE::NILSXP
177    }
178
179    fn to_real(&self) -> f64
180    where
181        Self: Sized,
182    {
183        0.
184    }
185
186    fn to_complex(&self) -> Rcomplex
187    where
188        Self: Sized,
189    {
190        Rcomplex { r: 0., i: 0. }
191    }
192
193    fn to_integer(&self) -> i32
194    where
195        Self: Sized,
196    {
197        i32::MIN
198    }
199
200    fn to_logical(&self) -> i32
201    where
202        Self: Sized,
203    {
204        i32::MIN
205    }
206
207    fn to_raw(&self) -> u8
208    where
209        Self: Sized,
210    {
211        0
212    }
213
214    fn to_sexp(&self) -> SEXP
215    where
216        Self: Sized,
217    {
218        unsafe { R_NilValue }
219    }
220}
221
222macro_rules! impl_real_tvv {
223    ($t: ty) => {
224        impl ToVectorValue for $t {
225            fn sexptype() -> SEXPTYPE {
226                SEXPTYPE::REALSXP
227            }
228
229            fn to_real(&self) -> f64 {
230                *self as f64
231            }
232        }
233
234        impl ToVectorValue for &$t {
235            fn sexptype() -> SEXPTYPE {
236                SEXPTYPE::REALSXP
237            }
238
239            fn to_real(&self) -> f64 {
240                **self as f64
241            }
242        }
243
244        impl ToVectorValue for Option<$t> {
245            fn sexptype() -> SEXPTYPE {
246                SEXPTYPE::REALSXP
247            }
248
249            fn to_real(&self) -> f64 {
250                if self.is_some() {
251                    self.unwrap() as f64
252                } else {
253                    unsafe { R_NaReal }
254                }
255            }
256        }
257    };
258}
259
260impl_real_tvv!(f64);
261impl_real_tvv!(f32);
262
263// Since these types might exceeds the max or min of R's 32bit integer, we need
264// to return as REALSXP
265impl_real_tvv!(i64);
266impl_real_tvv!(u32);
267impl_real_tvv!(u64);
268impl_real_tvv!(usize);
269
270macro_rules! impl_complex_tvv {
271    ($t: ty) => {
272        impl ToVectorValue for $t {
273            fn sexptype() -> SEXPTYPE {
274                SEXPTYPE::CPLXSXP
275            }
276
277            fn to_complex(&self) -> Rcomplex {
278                unsafe { std::mem::transmute(*self) }
279            }
280        }
281
282        impl ToVectorValue for &$t {
283            fn sexptype() -> SEXPTYPE {
284                SEXPTYPE::CPLXSXP
285            }
286
287            fn to_complex(&self) -> Rcomplex {
288                unsafe { std::mem::transmute(**self) }
289            }
290        }
291    };
292}
293
294impl_complex_tvv!(c64);
295impl_complex_tvv!(Rcplx);
296impl_complex_tvv!((f64, f64));
297
298macro_rules! impl_integer_tvv {
299    ($t: ty) => {
300        impl ToVectorValue for $t {
301            fn sexptype() -> SEXPTYPE {
302                SEXPTYPE::INTSXP
303            }
304
305            fn to_integer(&self) -> i32 {
306                *self as i32
307            }
308        }
309
310        impl ToVectorValue for &$t {
311            fn sexptype() -> SEXPTYPE {
312                SEXPTYPE::INTSXP
313            }
314
315            fn to_integer(&self) -> i32 {
316                **self as i32
317            }
318        }
319
320        impl ToVectorValue for Option<$t> {
321            fn sexptype() -> SEXPTYPE {
322                SEXPTYPE::INTSXP
323            }
324
325            fn to_integer(&self) -> i32 {
326                if self.is_some() {
327                    self.unwrap() as i32
328                } else {
329                    unsafe { R_NaInt }
330                }
331            }
332        }
333    };
334}
335
336impl_integer_tvv!(i8);
337impl_integer_tvv!(i16);
338impl_integer_tvv!(i32);
339impl_integer_tvv!(u16);
340
341impl ToVectorValue for u8 {
342    fn sexptype() -> SEXPTYPE {
343        SEXPTYPE::RAWSXP
344    }
345
346    fn to_raw(&self) -> u8 {
347        *self
348    }
349}
350
351impl ToVectorValue for &u8 {
352    fn sexptype() -> SEXPTYPE {
353        SEXPTYPE::RAWSXP
354    }
355
356    fn to_raw(&self) -> u8 {
357        **self
358    }
359}
360
361macro_rules! impl_str_tvv {
362    ($t: ty) => {
363        impl ToVectorValue for $t {
364            fn sexptype() -> SEXPTYPE {
365                SEXPTYPE::STRSXP
366            }
367
368            fn to_sexp(&self) -> SEXP
369            where
370                Self: Sized,
371            {
372                str_to_character(self.as_ref())
373            }
374        }
375
376        impl ToVectorValue for &$t {
377            fn sexptype() -> SEXPTYPE {
378                SEXPTYPE::STRSXP
379            }
380
381            fn to_sexp(&self) -> SEXP
382            where
383                Self: Sized,
384            {
385                str_to_character(self.as_ref())
386            }
387        }
388
389        impl ToVectorValue for Option<$t> {
390            fn sexptype() -> SEXPTYPE {
391                SEXPTYPE::STRSXP
392            }
393
394            fn to_sexp(&self) -> SEXP
395            where
396                Self: Sized,
397            {
398                if let Some(s) = self {
399                    str_to_character(s.as_ref())
400                } else {
401                    unsafe { R_NaString }
402                }
403            }
404        }
405    };
406}
407
408impl_str_tvv! {&str}
409impl_str_tvv! {String}
410
411impl ToVectorValue for Rstr {
412    fn sexptype() -> SEXPTYPE {
413        SEXPTYPE::STRSXP
414    }
415
416    fn to_sexp(&self) -> SEXP
417    where
418        Self: Sized,
419    {
420        unsafe { self.get() }
421    }
422}
423
424impl ToVectorValue for &Rstr {
425    fn sexptype() -> SEXPTYPE {
426        SEXPTYPE::STRSXP
427    }
428
429    fn to_sexp(&self) -> SEXP
430    where
431        Self: Sized,
432    {
433        unsafe { self.get() }
434    }
435}
436
437impl ToVectorValue for Option<Rstr> {
438    fn sexptype() -> SEXPTYPE {
439        SEXPTYPE::STRSXP
440    }
441
442    fn to_sexp(&self) -> SEXP
443    where
444        Self: Sized,
445    {
446        if let Some(s) = self {
447            unsafe { s.get() }
448        } else {
449            unsafe { R_NaString }
450        }
451    }
452}
453
454impl TryFrom<&Robj> for Rstr {
455    type Error = crate::Error;
456
457    fn try_from(robj: &Robj) -> Result<Self> {
458        let sexptype = robj.sexptype();
459        if let SEXPTYPE::STRSXP = sexptype {
460            if robj.len() == 1 {
461                let strs = Strings::try_from(robj)?;
462                Ok(strs.elt(0))
463            } else {
464                Err(Error::ExpectedRstr(robj.clone()))
465            }
466        } else if let SEXPTYPE::CHARSXP = sexptype {
467            Ok(Rstr { robj: robj.clone() })
468        } else {
469            Err(Error::ExpectedRstr(robj.clone()))
470        }
471    }
472}
473
474impl TryFrom<Robj> for Rstr {
475    type Error = crate::Error;
476
477    fn try_from(value: Robj) -> std::result::Result<Self, Self::Error> {
478        Self::try_from(&value)
479    }
480}
481
482impl GetSexp for Rstr {
483    unsafe fn get(&self) -> SEXP {
484        self.robj.get()
485    }
486
487    unsafe fn get_mut(&mut self) -> SEXP {
488        self.robj.get_mut()
489    }
490
491    fn as_robj(&self) -> &Robj {
492        &self.robj
493    }
494
495    fn as_robj_mut(&mut self) -> &mut Robj {
496        &mut self.robj
497    }
498}
499
500// These traits all derive from GetSexp with default implementations
501impl Length for Rstr {}
502impl Types for Rstr {}
503impl Conversions for Rstr {}
504impl Rinternals for Rstr {}
505impl Slices for Rstr {}
506impl Operators for Rstr {}
507
508impl ToVectorValue for bool {
509    fn sexptype() -> SEXPTYPE {
510        SEXPTYPE::LGLSXP
511    }
512
513    fn to_logical(&self) -> i32
514    where
515        Self: Sized,
516    {
517        *self as i32
518    }
519}
520
521impl ToVectorValue for &bool {
522    fn sexptype() -> SEXPTYPE {
523        SEXPTYPE::LGLSXP
524    }
525
526    fn to_logical(&self) -> i32
527    where
528        Self: Sized,
529    {
530        **self as i32
531    }
532}
533
534impl ToVectorValue for Rbool {
535    fn sexptype() -> SEXPTYPE {
536        SEXPTYPE::LGLSXP
537    }
538
539    fn to_logical(&self) -> i32
540    where
541        Self: Sized,
542    {
543        self.inner()
544    }
545}
546
547impl ToVectorValue for &Rbool {
548    fn sexptype() -> SEXPTYPE {
549        SEXPTYPE::LGLSXP
550    }
551
552    fn to_logical(&self) -> i32
553    where
554        Self: Sized,
555    {
556        self.inner()
557    }
558}
559
560impl ToVectorValue for Option<bool> {
561    fn sexptype() -> SEXPTYPE {
562        SEXPTYPE::LGLSXP
563    }
564
565    fn to_logical(&self) -> i32 {
566        if self.is_some() {
567            self.unwrap() as i32
568        } else {
569            unsafe { R_NaInt }
570        }
571    }
572}
573
574// Not thread safe.
575fn fixed_size_collect<I>(iter: I, len: usize) -> Robj
576where
577    I: Iterator,
578    I: Sized,
579    I::Item: ToVectorValue,
580{
581    single_threaded(|| unsafe {
582        // Length of the vector is known in advance.
583        let sexptype = I::Item::sexptype();
584        if sexptype != SEXPTYPE::NILSXP {
585            let res = Robj::alloc_vector(sexptype, len);
586            let sexp = res.get();
587            match sexptype {
588                SEXPTYPE::REALSXP => {
589                    let ptr = REAL(sexp);
590                    for (i, v) in iter.enumerate() {
591                        *ptr.add(i) = v.to_real();
592                    }
593                }
594                SEXPTYPE::CPLXSXP => {
595                    let ptr = COMPLEX(sexp);
596                    for (i, v) in iter.enumerate() {
597                        *ptr.add(i) = v.to_complex();
598                    }
599                }
600                SEXPTYPE::INTSXP => {
601                    let ptr = INTEGER(sexp);
602                    for (i, v) in iter.enumerate() {
603                        *ptr.add(i) = v.to_integer();
604                    }
605                }
606                SEXPTYPE::LGLSXP => {
607                    let ptr = LOGICAL(sexp);
608                    for (i, v) in iter.enumerate() {
609                        *ptr.add(i) = v.to_logical();
610                    }
611                }
612                SEXPTYPE::STRSXP => {
613                    for (i, v) in iter.enumerate() {
614                        SET_STRING_ELT(sexp, i as isize, v.to_sexp());
615                    }
616                }
617                SEXPTYPE::RAWSXP => {
618                    let ptr = RAW(sexp);
619                    for (i, v) in iter.enumerate() {
620                        *ptr.add(i) = v.to_raw();
621                    }
622                }
623                _ => {
624                    panic!("unexpected SEXPTYPE in collect_robj");
625                }
626            }
627            res
628        } else {
629            Robj::from(())
630        }
631    })
632}
633
634/// Extensions to iterators for R objects including [RobjItertools::collect_robj()].
635pub trait RobjItertools: Iterator {
636    /// Convert a wide range of iterators to Robj.
637    /// ```
638    /// use extendr_api::prelude::*;
639    ///
640    /// test! {
641    /// // Integer iterators.
642    /// let robj = (0..3).collect_robj();
643    /// assert_eq!(robj.as_integer_vector().unwrap(), vec![0, 1, 2]);
644    ///
645    /// // Logical iterators.
646    /// let robj = (0..3).map(|x| x % 2 == 0).collect_robj();
647    /// assert_eq!(robj.as_logical_vector().unwrap(), vec![TRUE, FALSE, TRUE]);
648    ///
649    /// // Numeric iterators.
650    /// let robj = (0..3).map(|x| x as f64).collect_robj();
651    /// assert_eq!(robj.as_real_vector().unwrap(), vec![0., 1., 2.]);
652    ///
653    /// // String iterators.
654    /// let robj = (0..3).map(|x| format!("{}", x)).collect_robj();
655    /// assert_eq!(robj.as_str_vector(), Some(vec!["0", "1", "2"]));
656    /// }
657    /// ```
658    fn collect_robj(self) -> Robj
659    where
660        Self: Iterator,
661        Self: Sized,
662        Self::Item: ToVectorValue,
663    {
664        if let (len, Some(max)) = self.size_hint() {
665            if len == max {
666                return fixed_size_collect(self, len);
667            }
668        }
669        // If the size is indeterminate, create a vector and call recursively.
670        let vec: Vec<_> = self.collect();
671        assert!(vec.iter().size_hint() == (vec.len(), Some(vec.len())));
672        vec.into_iter().collect_robj()
673    }
674
675    /// Collects an iterable into an [`RArray`].
676    /// The iterable must yield items column by column (aka Fortan order)
677    ///
678    /// # Arguments
679    ///
680    /// * `dims` - an array containing the length of each dimension
681    fn collect_rarray<const LEN: usize>(self, dims: [usize; LEN]) -> Result<RArray<Self::Item, LEN>>
682    where
683        Self: Iterator,
684        Self: Sized,
685        Self::Item: ToVectorValue,
686        Robj: for<'a> AsTypedSlice<'a, Self::Item>,
687    {
688        let mut vector = self.collect_robj();
689        let prod = dims.iter().product::<usize>();
690        if prod != vector.len() {
691            return Err(Error::Other(format!(
692                "The vector length ({}) does not match the length implied by the dimensions ({})",
693                vector.len(),
694                prod
695            )));
696        }
697        vector.set_attrib(wrapper::symbol::dim_symbol(), dims.iter().collect_robj())?;
698        let _data = vector.as_typed_slice().ok_or(Error::Other(
699            "Unknown error in converting to slice".to_string(),
700        ))?;
701        Ok(RArray::from_parts(vector))
702    }
703}
704
705// Thanks to *pretzelhammer* on stackoverflow for this.
706impl<T> RobjItertools for T where T: Iterator {}
707
708// Scalars which are ToVectorValue
709impl<T> From<T> for Robj
710where
711    T: ToVectorValue,
712{
713    fn from(scalar: T) -> Self {
714        Some(scalar).into_iter().collect_robj()
715    }
716}
717
718macro_rules! impl_from_as_iterator {
719    ($t: ty) => {
720        impl<T> From<$t> for Robj
721        where
722            $t: RobjItertools,
723            <$t as Iterator>::Item: ToVectorValue,
724            T: ToVectorValue,
725        {
726            fn from(val: $t) -> Self {
727                val.collect_robj()
728            }
729        }
730    };
731}
732
733// impl<T> From<Range<T>> for Robj
734// where
735//     Range<T> : RobjItertools,
736//     <Range<T> as Iterator>::Item: ToVectorValue,
737//     T : ToVectorValue
738// {
739//     fn from(val: Range<T>) -> Self {
740//         val.collect_robj()
741//     }
742// } //
743
744impl<T, const N: usize> From<[T; N]> for Robj
745where
746    T: ToVectorValue,
747{
748    fn from(val: [T; N]) -> Self {
749        fixed_size_collect(val.into_iter(), N)
750    }
751}
752
753impl<'a, T, const N: usize> From<&'a [T; N]> for Robj
754where
755    Self: 'a,
756    &'a T: ToVectorValue + 'a,
757{
758    fn from(val: &'a [T; N]) -> Self {
759        fixed_size_collect(val.iter(), N)
760    }
761}
762
763impl<'a, T, const N: usize> From<&'a mut [T; N]> for Robj
764where
765    Self: 'a,
766    &'a mut T: ToVectorValue + 'a,
767{
768    fn from(val: &'a mut [T; N]) -> Self {
769        fixed_size_collect(val.iter_mut(), N)
770    }
771}
772
773impl<T: ToVectorValue + Clone> From<&Vec<T>> for Robj {
774    fn from(value: &Vec<T>) -> Self {
775        let len = value.len();
776        fixed_size_collect(value.iter().cloned(), len)
777    }
778}
779
780impl<T: ToVectorValue> From<Vec<T>> for Robj {
781    fn from(value: Vec<T>) -> Self {
782        let len = value.len();
783        fixed_size_collect(value.into_iter(), len)
784    }
785}
786
787impl<'a, T> From<&'a [T]> for Robj
788where
789    Self: 'a,
790    T: 'a,
791    &'a T: ToVectorValue,
792{
793    fn from(val: &'a [T]) -> Self {
794        val.iter().collect_robj()
795    }
796}
797
798impl_from_as_iterator! {Range<T>}
799impl_from_as_iterator! {RangeInclusive<T>}
800
801impl From<Vec<Robj>> for Robj {
802    /// Convert a vector of Robj into a list.
803    fn from(val: Vec<Robj>) -> Self {
804        Self::from(&val)
805    }
806}
807
808impl From<&Vec<Robj>> for Robj {
809    fn from(val: &Vec<Robj>) -> Self {
810        List::from_values(val.iter()).into()
811    }
812}
813
814#[cfg(test)]
815mod test {
816    use super::*;
817    use crate as extendr_api;
818
819    #[test]
820    fn test_vec_rint_to_robj() {
821        test! {
822            let int_vec = vec![3,4,0,-2];
823            let int_vec_robj: Robj = int_vec.clone().into();
824            // unsafe { extendr_ffi::Rf_PrintValue(int_vec_robj.get())}
825            assert_eq!(int_vec_robj.as_integer_slice().unwrap(), &int_vec);
826
827            let rint_vec = vec![Rint::from(3), Rint::from(4), Rint::from(0), Rint::from(-2)];
828            let rint_vec_robj: Robj = rint_vec.into();
829            // unsafe { extendr_ffi::Rf_PrintValue(rint_vec_robj.get())}
830            assert_eq!(rint_vec_robj.as_integer_slice().unwrap(), &int_vec);
831        }
832    }
833
834    #[test]
835    fn test_collect_rarray_matrix() {
836        test! {
837            // Check that collect_rarray works the same as R's matrix() function
838            let rmat = (1i32..=16).collect_rarray([4, 4]);
839            assert!(rmat.is_ok());
840            assert_eq!(Robj::from(rmat), R!("matrix(1:16, nrow=4)").unwrap());
841        }
842    }
843
844    #[test]
845    fn test_collect_rarray_tensor() {
846        test! {
847            // Check that collect_rarray works the same as R's array() function
848            let rmat = (1i32..=16).collect_rarray([2, 4, 2]);
849            assert!(rmat.is_ok());
850            assert_eq!(Robj::from(rmat), R!("array(1:16, dim=c(2, 4, 2))").unwrap());
851        }
852    }
853
854    #[test]
855    fn test_collect_rarray_matrix_failure() {
856        test! {
857            // Check that collect_rarray fails when given an invalid shape
858            let rmat = (1i32..=16).collect_rarray([3, 3]);
859            assert!(rmat.is_err());
860            let msg = rmat.unwrap_err().to_string();
861            assert!(msg.contains('9'));
862            assert!(msg.contains("dimension"));
863        }
864    }
865
866    #[test]
867    fn test_collect_tensor_failure() {
868        test! {
869            // Check that collect_rarray fails when given an invalid shape
870            let rmat = (1i32..=16).collect_rarray([3, 3, 3]);
871            assert!(rmat.is_err());
872            let msg = rmat.unwrap_err().to_string();
873            assert!(msg.contains("27"));
874            assert!(msg.contains("dimension"));
875        }
876    }
877
878    #[test]
879    #[cfg(all(feature = "result_condition", not(feature = "result_list")))]
880    fn test_result_condition() {
881        use crate::prelude::*;
882        fn my_err_f() -> std::result::Result<f64, f64> {
883            Err(42.0) // return err float
884        }
885
886        test! {
887                  assert_eq!(
888                    r!(my_err_f()),
889                    R!(
890        "structure(list(message = 'extendr_err',
891        value = 42.0), class = c('extendr_error', 'error', 'condition'))"
892                    ).unwrap()
893                );
894            }
895    }
896
897    #[test]
898    #[cfg(feature = "result_list")]
899    fn test_result_list() {
900        use crate::prelude::*;
901        fn my_err_f() -> std::result::Result<f64, String> {
902            Err("We have water in the engine room!".to_string())
903        }
904
905        fn my_ok_f() -> std::result::Result<f64, String> {
906            Ok(123.123)
907        }
908
909        test! {
910            assert_eq!(
911                r!(my_err_f()),
912                R!("x=list(ok=NULL, err='We have water in the engine room!')
913                    class(x)='extendr_result'
914                    x"
915                ).unwrap()
916            );
917            assert_eq!(
918                r!(my_ok_f()),
919                R!("x = list(ok=123.123, err=NULL)
920                    class(x)='extendr_result'
921                    x"
922                ).unwrap()
923            );
924        }
925    }
926}