Repository: extendr/extendr
Pull request #713 (2024-03-24 19:20:49)

Implement IntoRobj for Dataframe<T>
This PR closes https://github.com/extendr/extendr/issues/710

It implements `IntoRobj` by returning the inner `Robj`. The associate test asserts that the returned Robj is equal to the Robj provided by `df.as_robj().clone()`.

JosiahParry: I believe the issues we are seeing here are related to https://github.com/r-lib/actions/issues/395

> Warning: Failed to download qpdf or ghostscript: Failed to install qpdf: Error: The process 'C:\ProgramData\Chocolatey\bin\choco.exe' failed with exit code 1

Patch:
diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
index dac27bae2..d70369deb 100644
--- a/extendr-api/src/robj/mod.rs
+++ b/extendr-api/src/robj/mod.rs
@@ -776,7 +776,7 @@ macro_rules! make_typed_slice {
                 match self.sexptype() {
                     $( $sexp )|* => {
                         unsafe {
-                            let ptr = $fn(self.get()) as *mut $type;
+                            let ptr = $fn(self.get_mut()) as *mut $type;
                             Some(std::slice::from_raw_parts_mut(ptr, self.len()))
                         }
                     }
diff --git a/extendr-api/src/wrapper/dataframe.rs b/extendr-api/src/wrapper/dataframe.rs
index 6c94e7535..482417632 100644
--- a/extendr-api/src/wrapper/dataframe.rs
+++ b/extendr-api/src/wrapper/dataframe.rs
@@ -62,3 +62,9 @@ where
         )
     }
 }
+
+impl<T> IntoRobj for Dataframe<T> {
+    fn into_robj(self) -> Robj {
+        self.robj
+    }
+}
diff --git a/extendr-api/src/wrapper/matrix.rs b/extendr-api/src/wrapper/matrix.rs
index b3e37ba3e..1a57230c3 100644
--- a/extendr-api/src/wrapper/matrix.rs
+++ b/extendr-api/src/wrapper/matrix.rs
@@ -40,6 +40,51 @@ pub type RColumn<T> = RArray<T, [usize; 1]>;
 pub type RMatrix<T> = RArray<T, [usize; 2]>;
 pub type RMatrix3D<T> = RArray<T, [usize; 3]>;
 
+impl<T> RMatrix<T>
+where
+    T: ToVectorValue,
+    Robj: for<'a> AsTypedSlice<'a, T>,
+{
+    /// Returns an [`RMatrix`] with dimensions according to `nrow` and `ncol`,
+    /// with arbitrary entries. To initialize a matrix containing only `NA`
+    /// values, use [`RMatrix::new_with_na`].
+    pub fn new(nrow: usize, ncol: usize) -> Self {
+        let sexptype = T::sexptype();
+        let matrix = Robj::alloc_matrix(sexptype, nrow as _, ncol as _);
+        // this is pretty much the same?
+        let mut robj = matrix;
+        let slice = robj.as_typed_slice_mut().unwrap();
+        let data = slice.as_mut_ptr();
+        RArray::from_parts(robj, data, [nrow, ncol])
+    }
+}
+
+impl<T> RMatrix<T>
+where
+    T: ToVectorValue + CanBeNA,
+    Robj: for<'a> AsTypedSlice<'a, T>,
+{
+    /// Returns an [`RMatrix`] with dimensions according to `nrow` and `ncol`,
+    /// with all entries set to `NA`.
+    ///
+    /// Note that since [`Raw`] does not have an NA representation in R,
+    /// this method is not implemented for [`Rbyte`].
+    pub fn new_with_na(nrow: usize, ncol: usize) -> Self {
+        let mut matrix = Self::new(nrow, ncol);
+        if nrow != 0 || ncol != 0 {
+            // matrix.data_mut().iter_mut().for_each(|x| {
+            matrix
+                .as_typed_slice_mut()
+                .unwrap()
+                .iter_mut()
+                .for_each(|x| {
+                    *x = T::na();
+                });
+        }
+        matrix
+    }
+}
+
 const BASE: usize = 0;
 
 trait Offset<D> {
@@ -369,8 +414,43 @@ impl<T, D> DerefMut for RArray<T, D> {
 
 #[cfg(test)]
 mod tests {
+    use extendr_engine::with_r;
+    use prelude::{Rcplx, Rfloat, Rint};
+
     use super::*;
 
+    #[test]
+    fn test_empty_matrix_new() {
+        dbg!("print like R");
+        with_r(|| {
+            // These are arbitrarily filled. We cannot create assertions for them.
+            let m: RMatrix<Rbyte> = RMatrix::new(5, 2); // possible!
+            unsafe { Rf_PrintValue(m.get()) };
+            let m: RMatrix<Rbool> = RMatrix::new(5, 2);
+            unsafe { Rf_PrintValue(m.get()) };
+            let m: RMatrix<Rint> = RMatrix::new(5, 2);
+            unsafe { Rf_PrintValue(m.get()) };
+            let m: RMatrix<Rfloat> = RMatrix::new(5, 2);
+            unsafe { Rf_PrintValue(m.get()) };
+            let m: RMatrix<Rcplx> = RMatrix::new(5, 2);
+            unsafe { Rf_PrintValue(m.get()) };
+
+            // let m: RMatrix<Rbyte> = RMatrix::new_with_na(10, 2); // not possible!
+            // unsafe { Rf_PrintValue(m.get()) };
+            let m: RMatrix<Rbool> = RMatrix::new_with_na(10, 2);
+            assert_eq!(R!("matrix(NA, 10, 2)").unwrap(), m.into_robj());
+
+            let m: RMatrix<Rint> = RMatrix::new_with_na(10, 2);
+            assert_eq!(R!("matrix(NA_integer_, 10, 2)").unwrap(), m.into_robj());
+
+            let m: RMatrix<Rfloat> = RMatrix::new_with_na(10, 2);
+            assert_eq!(R!("matrix(NA_real_, 10, 2)").unwrap(), m.into_robj());
+
+            let m: RMatrix<Rcplx> = RMatrix::new_with_na(10, 2);
+            assert_eq!(R!("matrix(NA_complex_, 10, 2)").unwrap(), m.into_robj());
+        });
+    }
+
     #[test]
     fn matrix_ops() {
         test! {
@@ -424,7 +504,7 @@ mod tests {
             vec![15.0, 16.0, 22.0, 3.0, 9.0].try_into().unwrap(),
         ];
         let (n_x, n_y) = (5, 5);
-        let matrix = RMatrix::new_matrix(n_x, n_y, |r, c| res[c][r]);
+        let _matrix = RMatrix::new_matrix(n_x, n_y, |r, c| res[c][r]);
 
         }
     }


Test patch:
diff --git a/extendr-api/tests/dataframe_tests.rs b/extendr-api/tests/dataframe_tests.rs
index 4216841ab..00c4eeedd 100644
--- a/extendr-api/tests/dataframe_tests.rs
+++ b/extendr-api/tests/dataframe_tests.rs
@@ -29,3 +29,25 @@ fn test_derive_into_dataframe() {
         assert_eq!(list[1], r!(["0", "1"]));
     }
 }
+
+#[test]
+fn test_into_robj_dataframe() {
+    test! {
+        use extendr_api::prelude::*;
+
+        #[derive(Clone, Debug, IntoDataFrameRow)]
+        struct MyStruct {
+            x: Rint,
+            y: Rstr,
+        }
+
+        let v = vec![MyStruct { x: 0.into(), y: "abc".into() }, MyStruct { x: 1.into(), y: "xyz".into() }];
+        let df = v.into_dataframe()?;
+
+        assert_eq!(
+            df.clone().as_robj().clone(),
+            df.into_robj()
+        );
+
+    }
+}