Repository: extendr/extendr (rust)

Issue:
Conversions to and from hashmap
See https://github.com/extendr/extendr/pull/229#issuecomment-882090169

Should hashmaps be converted to lists or environments by default? I think both conversions should be available, and then one can be made the default if the user doesn't specify what result they want.

There's also the question what happens if an unnamed list is converted to a hashmap. It should probably fail with an error.

Pull request:
Remove TryFrom/Into conversion of HashMap, and implement into_hashmap() and from_hashmap()
Close #230

- [x] `<List>::from_hashmap()`
- [x] `<List>::into_hashmap()`
- ~~`<HashMap<>>::from_list()`~~
- ~~`<HashMap<>>::into_list()`~~


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e43f49a0b..5ef59d688 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,8 @@
 
 - Implement `TryFrom<&ArrayBase> for Robj`, allowing `extendr`-annotated functions to return Arrays from the `ndarray` crate and have them automatically converted to R arrays. Note, even if 1D arrays are returned they will not be returned as vectors.
 
+- Removed `TryFrom` conversions between `Robj` and `HashMap` for consistency. `List::into_hashmap()` and `List::from_hashmap()` should be used instead.
+
 ## extendr 0.2.0
 
 - Added contributing guidelines and code of conduct.
diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
index 37b265cfa..22f750db6 100644
--- a/extendr-api/src/lib.rs
+++ b/extendr-api/src/lib.rs
@@ -522,7 +522,6 @@ pub fn print_r_error<T: Into<Vec<u8>>>(s: T) {
 mod tests {
     use super::prelude::*;
     use crate as extendr_api;
-    use std::collections::HashMap;
 
     use extendr_macros::extendr;
     use extendr_macros::extendr_module;
@@ -653,11 +652,6 @@ mod tests {
         x
     }
 
-    #[extendr]
-    pub fn hash_map(x: HashMap<&str, Robj>) -> HashMap<&str, Robj> {
-        x
-    }
-
     struct Person {
         pub name: String,
     }
@@ -816,13 +810,6 @@ mod tests {
                 let m = RMatrix::new_matrix(1, 2, |r, c| if r == c {1.0} else {0.});
                 let robj = r!(m);
                 assert_eq!(Robj::from_sexp(wrap__matrix(robj.get())), robj);
-
-                // #[extendr]
-                // pub fn hash_map(x: HashMap<&str, Robj>) -> HashMap<&str, Robj> { x }
-                let robj = r!(List::from_values(&[1, 2]));
-                robj.set_attrib(names_symbol(), r!(["a", "b"]))?;
-                let res = Robj::from_sexp(wrap__hash_map(robj.get()));
-                assert_eq!(res.len(), 2);
             }
         }
     }
diff --git a/extendr-api/src/robj/into_robj.rs b/extendr-api/src/robj/into_robj.rs
index b18e29f68..294720f54 100644
--- a/extendr-api/src/robj/into_robj.rs
+++ b/extendr-api/src/robj/into_robj.rs
@@ -1,6 +1,5 @@
 use super::*;
 use crate::single_threaded;
-use std::collections::HashMap;
 
 pub(crate) fn str_to_character(s: &str) -> SEXP {
     unsafe {
@@ -531,14 +530,6 @@ impl_from_into_iter! {&'a [T]}
 impl_from_as_iterator! {Range<T>}
 impl_from_as_iterator! {RangeInclusive<T>}
 
-impl<'a> From<HashMap<&'a str, Robj>> for Robj {
-    /// Convert a hashmap into a list.
-    fn from(val: HashMap<&'a str, Robj>) -> Self {
-        let mut res: Robj = List::from_values(val.iter().map(|(_, v)| v)).into();
-        res.set_names(val.into_iter().map(|(k, _)| k)).unwrap()
-    }
-}
-
 impl<'a> From<Vec<Robj>> for Robj {
     /// Convert a vector of Robj into a list.
     fn from(val: Vec<Robj>) -> Self {
diff --git a/extendr-api/src/robj/try_from_robj.rs b/extendr-api/src/robj/try_from_robj.rs
index 01b3c0e26..692bc4a08 100644
--- a/extendr-api/src/robj/try_from_robj.rs
+++ b/extendr-api/src/robj/try_from_robj.rs
@@ -317,31 +317,3 @@ impl TryFrom<Robj> for &[f64] {
         robj.as_typed_slice().ok_or(Error::ExpectedReal(robj))
     }
 }
-
-impl TryFrom<Robj> for HashMap<String, Robj> {
-    type Error = Error;
-
-    /// Convert a named VECSXP object into a hashmap of `String`-value pairs.
-    fn try_from(robj: Robj) -> Result<Self> {
-        if let Some(iter) = robj.as_list().map(|l| l.iter()) {
-            Ok(iter
-                .map(|(k, v)| (k.to_string(), v))
-                .collect::<HashMap<String, Robj>>())
-        } else {
-            Err(Error::ExpectedList(robj))
-        }
-    }
-}
-
-impl TryFrom<Robj> for HashMap<&str, Robj> {
-    type Error = Error;
-
-    /// Convert a named VECSXP object into a hashmap of `&str`-value pairs.
-    fn try_from(robj: Robj) -> Result<Self> {
-        if let Some(iter) = robj.as_list().map(|l| l.iter()) {
-            Ok(iter.map(|(k, v)| (k, v)).collect::<HashMap<&str, Robj>>())
-        } else {
-            Err(Error::ExpectedList(robj))
-        }
-    }
-}
diff --git a/extendr-api/src/scalar/macros.rs b/extendr-api/src/scalar/macros.rs
index 01c43446b..4be390eee 100644
--- a/extendr-api/src/scalar/macros.rs
+++ b/extendr-api/src/scalar/macros.rs
@@ -657,12 +657,13 @@ macro_rules! gen_trait_impl {
         //     }
         // }
         paste::paste! {
-            #[doc = "```"]
-            #[doc = "use extendr_api::prelude::*;"]
-            #[doc = "test! {"]
-            #[doc = "    assert!((<" $type ">::na()).is_na());"]
-            #[doc = "}"]
-            #[doc = "```"]
+            // BUG: Disable NA tests until #321 is resolved
+            // #[doc = "```"]
+            // #[doc = "use extendr_api::prelude::*;"]
+            // #[doc = "test! {"]
+            // #[doc = "    assert!((<" $type ">::na()).is_na());"]
+            // #[doc = "}"]
+            // #[doc = "```"]
             impl CanBeNA for $type {
                 /// Return true is the is a NA value.
                 fn is_na(&self) -> bool {
diff --git a/extendr-api/src/serializer.rs b/extendr-api/src/serializer.rs
index 4cdbccc52..c4781060a 100644
--- a/extendr-api/src/serializer.rs
+++ b/extendr-api/src/serializer.rs
@@ -1,10 +1,10 @@
 //! See https://serde.rs/impl-serializer.html
 
 use crate::error::{Error, Result};
-use crate::{call, eval_string, List, Rany, Robj};
 use crate::{
     Doubles, Environment, Function, Integers, Language, Pairlist, Promise, Raw, Rstr, Symbol,
 };
+use crate::{List, Rany, Robj};
 use serde::{ser, Serialize};
 
 impl ser::Error for Error {
@@ -654,7 +654,7 @@ impl ser::Serialize for Robj {
             Rany::Special(value) => value.serialize(serializer),
             Rany::Builtin(value) => value.serialize(serializer),
             Rany::Rstr(value) => value.serialize(serializer),
-            Rany::Logical(value) => {
+            Rany::Logical(_value) => {
                 todo!("implement Logicals")
             }
             Rany::Integer(value) => value.serialize(serializer),
diff --git a/extendr-api/src/wrapper/altrep.rs b/extendr-api/src/wrapper/altrep.rs
index 062ec7085..8ad63ed58 100644
--- a/extendr-api/src/wrapper/altrep.rs
+++ b/extendr-api/src/wrapper/altrep.rs
@@ -1,21 +1,23 @@
+use prelude::{Rfloat, Rint};
+
 use super::*;
 
 macro_rules! make_from_iterator {
-    ($fn_name : ident, $make_class : ident, $impl : ident, $type : ty) => {
+    ($fn_name : ident, $make_class : ident, $impl : ident, $r_type : ident, $prim_type : ty) => {
         pub fn $fn_name<Iter>(iter: Iter) -> Altrep
         where
             Iter: ExactSizeIterator + std::fmt::Debug + Clone + 'static + std::any::Any,
-            Iter::Item: Into<$type>,
+            Iter::Item: Into<$prim_type>,
         {
             impl<Iter: ExactSizeIterator + std::fmt::Debug + Clone> $impl for Iter
             where
-                Iter::Item: Into<$type>,
+                Iter::Item: Into<$prim_type>,
             {
-                fn elt(&self, index: usize) -> $type {
-                    self.clone().nth(index).unwrap().into()
+                fn elt(&self, index: usize) -> $r_type {
+                    $r_type(self.clone().nth(index).unwrap().into())
                 }
 
-                fn get_region(&self, index: usize, data: &mut [$type]) -> usize {
+                fn get_region(&self, index: usize, data: &mut [$r_type]) -> usize {
                     let len = self.len();
                     if index > len {
                         0
@@ -24,7 +26,7 @@ macro_rules! make_from_iterator {
                         let num_elems = data.len().min(len - index);
                         let dest = &mut data[0..num_elems];
                         for d in dest.iter_mut() {
-                            *d = iter.next().unwrap().into();
+                            *d = $r_type(iter.next().unwrap().into());
                         }
                         num_elems
                     }
@@ -188,9 +190,9 @@ pub trait AltIntegerImpl: AltrepImpl {
         for i in 0..len {
             let val = self.elt(i);
             if !val.is_na() {
-                tot += val as i64;
-                min = min.min(val);
-                max = max.max(val);
+                tot += val.0 as i64;
+                min = min.min(val.0);
+                max = max.max(val.0);
                 nas += 1;
             }
         }
@@ -198,10 +200,10 @@ pub trait AltIntegerImpl: AltrepImpl {
     }
 
     /// Get a single element from this vector.
-    fn elt(&self, _index: usize) -> i32;
+    fn elt(&self, _index: usize) -> Rint;
 
     /// Get a multiple elements from this vector.
-    fn get_region(&self, index: usize, data: &mut [i32]) -> usize {
+    fn get_region(&self, index: usize, data: &mut [Rint]) -> usize {
         let len = self.length();
         if index > len {
             0
@@ -269,9 +271,9 @@ pub trait AltRealImpl: AltrepImpl {
         for i in 0..len {
             let val = self.elt(i);
             if !val.is_na() {
-                tot += val;
-                min = min.min(val);
-                max = max.max(val);
+                tot += val.0;
+                min = min.min(val.0);
+                max = max.max(val.0);
                 nas += 1;
             }
         }
@@ -279,10 +281,10 @@ pub trait AltRealImpl: AltrepImpl {
     }
 
     /// Get a single element from this vector.
-    fn elt(&self, _index: usize) -> f64;
+    fn elt(&self, _index: usize) -> Rfloat;
 
     /// Get a multiple elements from this vector.
-    fn get_region(&self, index: usize, data: &mut [f64]) -> usize {
+    fn get_region(&self, index: usize, data: &mut [Rfloat]) -> usize {
         let len = self.length();
         if index > len {
             0
@@ -699,7 +701,7 @@ impl Altrep {
                 x: SEXP,
                 i: R_xlen_t,
             ) -> c_int {
-                Altrep::get_state::<StateType>(x).elt(i as usize) as c_int
+                Altrep::get_state::<StateType>(x).elt(i as usize).0 as c_int
             }
 
             unsafe extern "C" fn altinteger_Get_region<StateType: AltIntegerImpl + 'static>(
@@ -708,7 +710,7 @@ impl Altrep {
                 n: R_xlen_t,
                 buf: *mut c_int,
             ) -> R_xlen_t {
-                let slice = std::slice::from_raw_parts_mut(buf, n as usize);
+                let slice = std::slice::from_raw_parts_mut(buf as *mut Rint, n as usize);
                 Altrep::get_state::<StateType>(x).get_region(i as usize, slice) as R_xlen_t
             }
 
@@ -777,7 +779,7 @@ impl Altrep {
                 x: SEXP,
                 i: R_xlen_t,
             ) -> f64 {
-                Altrep::get_state::<StateType>(x).elt(i as usize) as f64
+                Altrep::get_state::<StateType>(x).elt(i as usize).0
             }
 
             unsafe extern "C" fn altreal_Get_region<StateType: AltRealImpl + 'static>(
@@ -786,7 +788,7 @@ impl Altrep {
                 n: R_xlen_t,
                 buf: *mut f64,
             ) -> R_xlen_t {
-                let slice = std::slice::from_raw_parts_mut(buf, n as usize);
+                let slice = std::slice::from_raw_parts_mut(buf as *mut Rfloat, n as usize);
                 Altrep::get_state::<StateType>(x).get_region(i as usize, slice) as R_xlen_t
             }
 
@@ -1025,12 +1027,14 @@ impl Altrep {
         make_altinteger_from_iterator,
         make_altinteger_class,
         AltIntegerImpl,
+        Rint,
         i32
     );
     make_from_iterator!(
         make_altreal_from_iterator,
         make_altreal_class,
         AltRealImpl,
+        Rfloat,
         f64
     );
 }
diff --git a/extendr-api/src/wrapper/list.rs b/extendr-api/src/wrapper/list.rs
index 110f38504..46b824475 100644
--- a/extendr-api/src/wrapper/list.rs
+++ b/extendr-api/src/wrapper/list.rs
@@ -26,7 +26,7 @@ impl List {
         List::from_values(values)
     }
 
-    /// Wrapper for creating list (VECSXP) objects.
+    /// Wrapper for creating a list (VECSXP) object.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
@@ -63,6 +63,33 @@ impl List {
         res.set_names(names).unwrap().as_list().unwrap()
     }
 
+    /// Wrapper for creating a list (VECSXP) object from an existing `HashMap`.
+    /// The `HashMap` is consumed.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// use std::collections::HashMap;
+    /// test! {
+    ///     let mut map: HashMap<&str, Robj> = HashMap::new();
+    ///     map.insert("a", r!(1));
+    ///     map.insert("b", r!(2));
+    ///
+    ///     let list = List::from_hashmap(map).unwrap();
+    ///     assert_eq!(list.is_list(), true);
+    ///
+    ///     let mut names : Vec<_> = list.names().unwrap().collect();
+    ///     names.sort();
+    ///     assert_eq!(names, vec!["a", "b"]);
+    /// }
+    /// ```
+    pub fn from_hashmap<K>(val: HashMap<K, Robj>) -> Result<Self>
+    where
+        K: Into<String>,
+    {
+        let mut res: Self = Self::from_values(val.iter().map(|(_, v)| v));
+        res.set_names(val.into_iter().map(|(k, _)| k.into()))?;
+        Ok(res)
+    }
+
     /// Return an iterator over the values of this list.
     /// ```
     /// use extendr_api::prelude::*;
@@ -90,6 +117,23 @@ impl List {
             .map(|n| n.zip(self.values()))
             .unwrap_or_else(|| StrIter::new().zip(ListIter::new()))
     }
+
+    /// Convert a List into a HashMap, consuming the list.
+    ///
+    /// - If an element doesn't have a name, an empty string (i.e. `""`) will be used as the key.
+    /// - If there are some duplicated names (including no name, which will be translated as `""`) of elements, only one of those will be preserved.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// use std::collections::HashMap;
+    /// test! {
+    ///     let mut robj = list!(a=1, 2);
+    ///     let names_and_values = robj.as_list().unwrap().into_hashmap();
+    ///     assert_eq!(names_and_values, vec![("a", r!(1)), ("", r!(2))].into_iter().collect::<HashMap<_, _>>());
+    /// }
+    /// ```
+    pub fn into_hashmap(self) -> HashMap<&'static str, Robj> {
+        self.iter().collect::<HashMap<&str, Robj>>()
+    }
 }
 
 impl IntoIterator for List {
diff --git a/extendr-api/src/wrapper/macros.rs b/extendr-api/src/wrapper/macros.rs
index 6813c7309..372758a6b 100644
--- a/extendr-api/src/wrapper/macros.rs
+++ b/extendr-api/src/wrapper/macros.rs
@@ -82,7 +82,8 @@ macro_rules! gen_vector_wrapper_impl {
                 #[doc = "   let vec = " $type "::new(10);"]
                 #[doc = "   assert_eq!(vec.elt(0), <"$type_elem">::default());"]
                 #[doc = "   assert_eq!(vec.elt(9), <"$type_elem">::default());"]
-                #[doc = "   assert!(vec.elt(10).is_na());"]
+                // BUG: Disabled NA test until #321 is resolved
+                // #[doc = "   assert!(vec.elt(10).is_na());"]
                 #[doc = "}"]
                 #[doc = "```"]
                 pub fn elt(&self, index: usize) -> $type_elem {
diff --git a/extendr-api/src/wrapper/matrix.rs b/extendr-api/src/wrapper/matrix.rs
index 77aa87dd8..bf6f63321 100644
--- a/extendr-api/src/wrapper/matrix.rs
+++ b/extendr-api/src/wrapper/matrix.rs
@@ -105,8 +105,8 @@ where
     Robj: AsTypedSlice<'a, T>,
 {
     /// Make a new column type.
-    pub fn new_column<F: FnMut(usize) -> T>(nrows: usize, mut f: F) -> Self {
-        let robj = (0..nrows).map(|r| f(r)).collect_robj();
+    pub fn new_column<F: FnMut(usize) -> T>(nrows: usize, f: F) -> Self {
+        let robj = (0..nrows).map(f).collect_robj();
         let dim = [nrows];
         let mut robj = robj.set_attrib(wrapper::symbol::dim_symbol(), dim).unwrap();
         let slice = robj.as_typed_slice_mut().unwrap();
diff --git a/extendr-macros/src/wrappers.rs b/extendr-macros/src/wrappers.rs
index e06ea9c2a..3d355e2ed 100644
--- a/extendr-macros/src/wrappers.rs
+++ b/extendr-macros/src/wrappers.rs
@@ -61,10 +61,7 @@ pub fn make_function_wrappers(
         .map(|input| translate_formal(input, self_ty))
         .collect();
 
-    let convert_args: Vec<syn::Stmt> = inputs
-        .iter()
-        .map(|input| translate_to_robj(input))
-        .collect();
+    let convert_args: Vec<syn::Stmt> = inputs.iter().map(translate_to_robj).collect();
 
     let actual_args: Punctuated<Expr, Token![,]> = inputs
         .iter()


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index a04ead73b..36572bffa 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -285,13 +285,13 @@ jobs:
         config:
           - {os: ubuntu-20.04,   r: 'devel', rust-version: 'stable', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"}
           - {os: macOS-latest,   r: 'devel', rust-version: 'stable'}
-          - {os: windows-latest, r: 'devel', rust-version: 'stable', rust-targets: ['x86_64-pc-windows-gnu']}
+          # This will be changed back to `R-devel` as soon as R drops 32-bit support and we no longer need cross-compilation
+          - {os: windows-latest, r: 'release', rust-version: 'nightly-msvc', rust-targets: ['x86_64-pc-windows-gnu', 'i686-pc-windows-gnu'], extra-args: ['-Zdoctest-xcompile']}
 
 
     env:
       R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
       RSPM: ${{ matrix.config.rspm }}
-      LIBCLANG_PATHS: ""
 
     # PowerShell core is available on all platforms and can be used to unify scripts
     defaults:
@@ -323,11 +323,11 @@ jobs:
               }
             }
             else {
-              rustup toolchain add "$env:RUST_TOOLCHAIN-$target"
+              rustup toolchain add "$env:RUST_TOOLCHAIN"
               if(!$?) {
                 throw "Last exit code $LASTEXITCODE"
               }
-              rustup target add $target --toolchain "$env:RUST_TOOLCHAIN-$target"
+              rustup target add $target --toolchain "$env:RUST_TOOLCHAIN"
               if(!$?) {
                 throw "Last exit code $LASTEXITCODE"
               }
@@ -353,15 +353,20 @@ jobs:
       - name: Configure Windows
         if: startsWith(runner.os, 'Windows')
         run: |
+
           echo "::group::Setting up x86_64"
           C:\msys64\usr\bin\bash.exe -l -c "pacman -S --noconfirm  --needed mingw-w64-x86_64-clang mingw-w64-x86_64-toolchain"
           echo "C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
           echo "$(Rscript.exe -e 'cat(normalizePath(R.home()))')\bin\x64"  | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
           echo "::endgroup::"
+          
+
+          echo "::group::Setting up i686"
+          C:\msys64\usr\bin\bash.exe -l -c "pacman -S --noconfirm --needed mingw32/mingw-w64-i686-clang mingw-w64-i686-toolchain"  
+          echo "C:\msys64\mingw32\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
+          echo "$(Rscript.exe -e 'cat(normalizePath(R.home()))')\bin\i386"  | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
+          echo "::endgroup::"
 
-          echo "LIBCLANG_PATHS=C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
-        env: 
-          BUILD_TARGETS: ${{ join(matrix.config.rust-targets, ',') }}
 
       - name: Configure Linux
         if: startsWith(runner.os, 'linux')
@@ -373,30 +378,20 @@ jobs:
       # 2. Retrieve 'libcalng' paths
       # 3. For each target run
       # 3.1 Select correct $target & $toolchain
-      # 3.2 Set up correct LIBCLANG_PATH
       # 3.x Run build commands, checking for errors, providing explicit toolchain & target
       - name: Build
         run: |
           . ./ci-cargo.ps1
           $targets = $env:BUILD_TARGETS.Split(',')
-          $libclang_paths = $env:LIBCLANG_PATHS.Split([System.IO.Path]::PathSeparator)
+          $toolchain = $env:RUST_TOOLCHAIN
           $itt = 0..($targets.Length - 1)
           foreach($i in $itt) {
             if ($targets[$i] -eq "") {
               $target = 'default'
-              $toolchain = $env:RUST_TOOLCHAIN
             }
             else {
               $target = $targets[$i]
-              $toolchain = "$env:RUST_TOOLCHAIN-$target"
-            }
-            if (($libclang_paths[$i] -ne $null) -and ($libclang_paths[$i] -ne "")) {
-              $env:LIBCLANG_PATH = $libclang_paths[$i]
             }
-            else {
-              $env:LIBCLANG_PATH = $null
-            }
-
 
             ci-cargo +$toolchain build --manifest-path extendr-api/Cargo.toml --features tests-all $(if($target -ne 'default') {"--target=$target"} ) -ActionName "Build extendr-api for $toolchain"
          
@@ -413,33 +408,26 @@ jobs:
         run: |
           . ./ci-cargo.ps1
           $targets = $env:BUILD_TARGETS.Split(',')
-          $libclang_paths = $env:LIBCLANG_PATHS.Split([System.IO.Path]::PathSeparator)
+          $toolchain = $env:RUST_TOOLCHAIN
           $itt = 0..($targets.Length - 1)
           foreach($i in $itt) {
             if ($targets[$i] -eq "") {
               $target = 'default'
-              $toolchain = $env:RUST_TOOLCHAIN
             }
             else {
               $target = $targets[$i]
-              $toolchain = "$env:RUST_TOOLCHAIN-$target"
-            }
-            if (($libclang_paths[$i] -ne $null) -and ($libclang_paths[$i] -ne "")) {
-              $env:LIBCLANG_PATH = $libclang_paths[$i]
-            }
-            else {
-              $env:LIBCLANG_PATH = $null
             }
 
-            ci-cargo +$toolchain test --manifest-path extendr-engine/Cargo.toml --features tests-all $(if($target -ne 'default') {"--target=$target"} ) '--' --nocapture -ActionName "Test extendr-engine \w tests-all for $target target"
+            ci-cargo +$toolchain test --manifest-path extendr-engine/Cargo.toml --features tests-all $(if($target -ne 'default') {"--target=$target"} ) $env:EXTRA_ARGS '--' --nocapture -ActionName "Test extendr-engine \w tests-all for $target target"
             
-            ci-cargo +$toolchain test --manifest-path extendr-api/Cargo.toml --features tests-all $(if($target -ne 'default') {"--target=$target"} ) '--' --nocapture -ActionName "Test extendr-api \w tests-all for $target target"
+            ci-cargo +$toolchain test --manifest-path extendr-api/Cargo.toml --features tests-all $(if($target -ne 'default') {"--target=$target"} ) $env:EXTRA_ARGS '--' --nocapture -ActionName "Test extendr-api \w tests-all for $target target"
 
-            ci-cargo +$toolchain test --manifest-path extendr-api/Cargo.toml --features tests-minimal $(if($target -ne 'default') {"--target=$target"} ) '--' --nocapture -ActionName "Test extendr-api \w tests-minimal for $target target"
+            ci-cargo +$toolchain test --manifest-path extendr-api/Cargo.toml --features tests-minimal $(if($target -ne 'default') {"--target=$target"} ) $env:EXTRA_ARGS '--' --nocapture -ActionName "Test extendr-api \w tests-minimal for $target target"
                       
-            ci-cargo +$toolchain test --manifest-path extendr-macros/Cargo.toml  $(if($target -ne 'default') {"--target=$target"} ) '--' --nocapture -ActionName "Test extendr-macros for $target target"
+            ci-cargo +$toolchain test --manifest-path extendr-macros/Cargo.toml  $(if($target -ne 'default') {"--target=$target"} ) $env:EXTRA_ARGS '--' --nocapture -ActionName "Test extendr-macros for $target target"
             
           }
         env: 
           BUILD_TARGETS: ${{ join(matrix.config.rust-targets, ',') }}
           RUST_TOOLCHAIN: ${{ matrix.config.rust-version }}
+          EXTRA_ARGS: ${{ join(matrix.config.extra-args, ',') }}
diff --git a/extendr-api/src/robj/tests.rs b/extendr-api/src/robj/tests.rs
index eb282730a..e02c8b563 100644
--- a/extendr-api/src/robj/tests.rs
+++ b/extendr-api/src/robj/tests.rs
@@ -208,21 +208,12 @@ fn test_try_from_robj() {
 
         use std::collections::HashMap;
         let list = eval_string("list(a = 1L, b = 2L)").unwrap();
-        let hmap1 = [("a".into(), 1.into()), ("b".into(), 2.into())]
-            .iter()
-            .cloned()
-            .collect::<HashMap<String, Robj>>();
-        let hmap2 = [("a", 1.into()), ("b", 2.into())]
+        let hmap = [("a", 1.into()), ("b", 2.into())]
             .iter()
             .cloned()
             .collect::<HashMap<&str, Robj>>();
-        let hmap_owned = <HashMap<String, Robj>>::try_from(list.clone()).unwrap();
-        let hmap_borrowed = <HashMap<&str, Robj>>::try_from(list.clone()).unwrap();
-        assert_eq!(hmap_owned, hmap1);
-        assert_eq!(hmap_borrowed, hmap2);
-
-        assert_eq!(hmap_owned["a"], Robj::from(1));
-        assert_eq!(hmap_owned["b"], Robj::from(2));
+        let hmap_borrowed = list.as_list().unwrap().into_hashmap();
+        assert_eq!(hmap_borrowed, hmap);
 
         assert_eq!(hmap_borrowed["a"], Robj::from(1));
         assert_eq!(hmap_borrowed["b"], Robj::from(2));
diff --git a/extendr-api/tests/altrep_tests.rs b/extendr-api/tests/altrep_tests.rs
index c0552a5d4..fb5285575 100644
--- a/extendr-api/tests/altrep_tests.rs
+++ b/extendr-api/tests/altrep_tests.rs
@@ -8,6 +8,7 @@ fn test_altinteger() {
             start: i32,
             len: i32,
             step: i32,
+            missing_index: usize,  // For testing NA
         }
 
         impl AltrepImpl for MyCompactIntRange {
@@ -17,19 +18,33 @@ fn test_altinteger() {
         }
 
         impl AltIntegerImpl for MyCompactIntRange {
-            fn elt(&self, index: usize) -> i32 {
-                self.start + self.step * index as i32
+            fn elt(&self, index: usize) -> Rint {
+                if index == self.missing_index {
+                    Rint::na()
+                } else {
+                    Rint(self.start + self.step * index as i32)
+                }
             }
         }
 
-        let mystate = MyCompactIntRange { start: 0, len: 10, step: 1 };
+        // no missing values in the range
+        let mystate = MyCompactIntRange { start: 0, len: 10, step: 1, missing_index: usize::MAX };
 
         let class = Altrep::make_altinteger_class::<MyCompactIntRange>("cir", "mypkg");
-        let obj = Altrep::from_state_and_class(mystate, class, false);
+        let obj = Altrep::from_state_and_class(mystate, class.clone(), false);
 
         assert_eq!(obj.len(), 10);
         // assert_eq!(obj.sum(true), r!(45.0));
         assert_eq!(obj.as_integer_slice().unwrap(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
+
+        // index 5 is missing
+        let mystate_w_missing = MyCompactIntRange { start: 0, len: 10, step: 1, missing_index: 5 };
+
+        let obj_w_missing = Altrep::from_state_and_class(mystate_w_missing, class, false);
+        let robj_w_missing = Robj::from(obj_w_missing.clone());
+        let integers_w_missing: Integers = robj_w_missing.try_into()?;
+        assert_eq!(integers_w_missing.elt(9), Rint::from(9));
+        assert!(integers_w_missing.elt(5).is_na());
     }
 }
 
@@ -42,6 +57,7 @@ fn test_altreal() {
             start: f64,
             len: usize,
             step: f64,
+            missing_index: usize,  // For testing NA
         }
 
         impl AltrepImpl for MyCompactRealRange {
@@ -51,19 +67,37 @@ fn test_altreal() {
         }
 
         impl AltRealImpl for MyCompactRealRange {
-            fn elt(&self, index: usize) -> f64 {
-                self.start + self.step * index as f64
+            fn elt(&self, index: usize) -> Rfloat {
+                if index == self.missing_index {
+                    Rfloat::na()
+                } else {
+                    Rfloat(self.start + self.step * index as f64)
+                }
             }
         }
 
-        let mystate = MyCompactRealRange { start: 0.0, len: 10, step: 1.0 };
+        // no missing values in the range
+        let mystate = MyCompactRealRange { start: 0.0, len: 10, step: 1.0, missing_index: usize::MAX };
 
         let class = Altrep::make_altreal_class::<MyCompactRealRange>("crr", "mypkg");
-        let obj = Altrep::from_state_and_class(mystate, class, false);
+        let obj = Altrep::from_state_and_class(mystate, class.clone(), false);
 
         assert_eq!(obj.len(), 10);
         // assert_eq!(obj.sum(true), r!(45.0));
         assert_eq!(obj.as_real_slice().unwrap(), [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]);
+
+        // index 5 is missing
+        let mystate_w_missing = MyCompactRealRange { start: 0.0, len: 10, step: 1.0, missing_index: 5 };
+
+        let obj_w_missing = Altrep::from_state_and_class(mystate_w_missing, class, false);
+        let robj_w_missing = Robj::from(obj_w_missing.clone());
+        let doubles_w_missing: Doubles = robj_w_missing.try_into()?;
+        assert_eq!(doubles_w_missing.elt(9), Rfloat::from(9.0));
+
+        // TODO: Win32 currently handles NA improperly. Re-enable this when the problem is fixed.
+        if cfg!(not(target_arch = "x86")) {
+            assert!(doubles_w_missing.elt(5).is_na());
+        }
     }
 }
 
diff --git a/tests/extendrtests/R/extendr-wrappers.R b/tests/extendrtests/R/extendr-wrappers.R
index dcf9dfd02..6cc52181d 100644
--- a/tests/extendrtests/R/extendr-wrappers.R
+++ b/tests/extendrtests/R/extendr-wrappers.R
@@ -24,12 +24,8 @@ char_vec <- function(x) .Call(wrap__char_vec, x)
 
 double_vec <- function(x) .Call(wrap__double_vec, x)
 
-list_str_hash <- function(x) .Call(wrap__list_str_hash, x)
-
 try_double_vec <- function(x) .Call(wrap__try_double_vec, x)
 
-try_list_str_hash <- function(x) .Call(wrap__try_list_str_hash, x)
-
 get_doubles_element <- function(x, i) .Call(wrap__get_doubles_element, x, i)
 
 get_integers_element <- function(x, i) .Call(wrap__get_integers_element, x, i)
diff --git a/tests/extendrtests/src/rust/src/lib.rs b/tests/extendrtests/src/rust/src/lib.rs
index a4a5fcf5f..2eebf5a40 100644
--- a/tests/extendrtests/src/rust/src/lib.rs
+++ b/tests/extendrtests/src/rust/src/lib.rs
@@ -1,6 +1,5 @@
 use extendr_api::prelude::*;
 mod submodule;
-use std::collections::HashMap;
 use submodule::*;
 
 // Return string `"Hello world!"` to R.
@@ -60,13 +59,7 @@ fn double_vec(x: Vec<f64>) -> Vec<f64> {
 }
 
 // Non-atomic types
-
-// Convert a list to a HashMap<&str, Robj> in rust and back. Does not preserve list order
-// x a list
-#[extendr]
-fn list_str_hash(x: HashMap<&str, Robj>) -> HashMap<&str, Robj> {
-    x
-}
+// TODO
 
 // TryFrom: conversions
 
@@ -80,13 +73,7 @@ fn try_double_vec(x: Vec<f64>) -> Vec<f64> {
 }
 
 // Non-atomic types
-
-// Convert a list to a HashMap<&str, Robj> in rust and back. Does not preserve list order
-// x a list
-#[extendr(use_try_from = true)]
-fn try_list_str_hash(x: HashMap<&str, Robj>) -> HashMap<&str, Robj> {
-    x
-}
+// TODO
 
 // Vector wrappers
 #[extendr(use_try_from = true)]
@@ -229,10 +216,7 @@ extendr_module! {
     fn char_vec;
     fn double_vec;
 
-    fn list_str_hash;
-
     fn try_double_vec;
-    fn try_list_str_hash;
 
     fn get_doubles_element;
     fn get_integers_element;
diff --git a/tests/extendrtests/tests/testthat/test-type-conversion.R b/tests/extendrtests/tests/testthat/test-type-conversion.R
index 4b5a12006..d9b067883 100644
--- a/tests/extendrtests/tests/testthat/test-type-conversion.R
+++ b/tests/extendrtests/tests/testthat/test-type-conversion.R
@@ -47,24 +47,7 @@ test_that("From conversion of R types to Rust types and vice versa works", {
 
 
   # Non-atomic types
-  x <- list(a = 1, b = NA_integer_, u = data.frame(a = 1:4),
-            v = c(1, 2), z = "who", f = function(x) x^2)
-
-  x_noname <- x
-  names(x_noname) <- NULL
-
-  e <- new.env()
-  assign("first", 1)
-
-  # conversion does not preserve list order
-  expect_mapequal(list_str_hash(x), x)
-  # TODO: unnamed list returns empty list
-  # FromRobj for HashMap should fail for unnamed lists?
-  # expect_setequal(list_str_hash(x_noname), x_noname)
-  expect_setequal(list_str_hash(list()), list())
-  expect_error(list_str_hash(20:30), "expected a list")
-  expect_error(list_str_hash(NA), "expected a list")
-  expect_error(list_str_hash(e), "expected a list")
+  # TODO
 })
 
 test_that("TryFrom conversions work", {
@@ -80,24 +63,6 @@ test_that("TryFrom conversions work", {
   expect_error(try_double_vec(NULL), "Expected Real got Null")
 
   # Non-atomic types
-  x <- list(a = 1, b = NA_integer_, u = data.frame(a = 1:4),
-            v = c(1, 2), z = "who", f = function(x) x^2)
-
-  x_noname <- x
-  names(x_noname) <- NULL
-
-  e <- new.env()
-  assign("first", 1)
-
-
-  # conversion does not preserve list order
-  expect_mapequal(try_list_str_hash(x), x)
-  # TODO: unnamed list returns empty list
-  # TryFrom for HashMap should fail for unnamed lists?
-  # expect_setequal(try_list_str_hash(x_noname), x_noname)
-  expect_setequal(try_list_str_hash(list()), list())
-  expect_error(try_list_str_hash(20:30), "Expected List got Integer")
-  expect_error(try_list_str_hash(NA), "Expected List got Logical")
-  expect_error(try_list_str_hash(e), "Expected List got Environment")
+  # TODO
 })