Repository: extendr/extendr (rust)

Issue:
Why doesn't AltrepImpl::get_region() accept the starting index?
@andy-thomason 
Are there any reason why this `get_region()` accept only the size of the region (`index`) and assume the starting index is always zero?

https://github.com/extendr/extendr/blob/67060ca864b9effab8eedac057cb9d614b9d6a6e/extendr-api/src/wrapper/altrep.rs#L204

Pull request description:
Fix Altrep's implementation of get_region()
Close #311

Thanks @andy-thomason for spotting the cause!

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/error.rs b/extendr-api/src/error.rs
index 1b5477db2..cd267ed96 100644
--- a/extendr-api/src/error.rs
+++ b/extendr-api/src/error.rs
@@ -2,6 +2,8 @@
 
 use crate::{throw_r_error, Robj};
 
+use std::convert::Infallible;
+
 /// Throw an R error if a result is an error.
 #[doc(hidden)]
 pub fn unwrap_or_throw<T>(r: std::result::Result<T, &'static str>) -> T {
@@ -193,3 +195,9 @@ impl From<String> for Error {
 //         Error::None
 //     }
 // }
+
+impl From<Infallible> for Error {
+    fn from(_: Infallible) -> Self {
+        Error::Other("".to_string())
+    }
+}
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/rmacros.rs b/extendr-api/src/rmacros.rs
index bbb2ed61c..1ea4e2e84 100644
--- a/extendr-api/src/rmacros.rs
+++ b/extendr-api/src/rmacros.rs
@@ -91,28 +91,6 @@ macro_rules! sym {
     };
 }
 
-/// Create a list.
-///
-/// Example:
-/// ```
-/// use extendr_api::prelude::*;
-/// test! {
-///     let mylist = list!(x=1, y=2);
-///     assert_eq!(mylist, r!(List::from_pairs(vec![("x", r!(1)), ("y", r!(2))])));
-/// }
-/// ```
-///
-/// Panics on error.
-#[macro_export]
-macro_rules! list {
-    () => {
-        call!("list").unwrap()
-    };
-    ($($rest: tt)*) => {
-        call!("list", $($rest)*).unwrap()
-    };
-}
-
 /// Create a dataframe.
 ///
 /// Example:
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/mod.rs b/extendr-api/src/robj/mod.rs
index b40bd2020..3bd1ee268 100644
--- a/extendr-api/src/robj/mod.rs
+++ b/extendr-api/src/robj/mod.rs
@@ -710,6 +710,8 @@ make_typed_slice!(Rint, INTEGER, INTSXP);
 make_typed_slice!(f64, REAL, REALSXP);
 make_typed_slice!(Rfloat, REAL, REALSXP);
 make_typed_slice!(u8, RAW, RAWSXP);
+make_typed_slice!(Robj, VECTOR_PTR, VECSXP);
+make_typed_slice!(Rstr, STRING_PTR, STRSXP);
 
 /// These are helper functions which give access to common properties of R objects.
 #[allow(non_snake_case)]
@@ -806,10 +808,12 @@ impl Robj {
     {
         let iter = names.into_iter();
         let robj = iter.collect_robj();
-        if robj.len() == self.len() {
-            self.set_attrib(wrapper::symbol::names_symbol(), robj)
-        } else {
+        if !robj.is_vector() && !robj.is_pairlist() {
+            Err(Error::ExpectedVector(robj))
+        } else if robj.len() != self.len() {
             Err(Error::NamesLengthMismatch(robj))
+        } else {
+            self.set_attrib(wrapper::symbol::names_symbol(), robj)
         }
     }
 
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 4be390eee..2066f62a9 100644
--- a/extendr-api/src/scalar/macros.rs
+++ b/extendr-api/src/scalar/macros.rs
@@ -459,7 +459,9 @@ macro_rules! gen_binopassign {
                 fn [< $opname:snake >](&mut self, other: $type) {
                     match (*self, other.into()) {
                         (Some(lhs), Some(rhs)) => {
-                            *self = $expr(lhs, rhs);
+                            let f = $expr;
+                            let _ = (); // confuse clippy.
+                            *self = f(lhs, rhs);
                         },
                         _ => *self = None,
                     }
diff --git a/extendr-api/src/wrapper/altrep.rs b/extendr-api/src/wrapper/altrep.rs
index 8ad63ed58..9dd69ac35 100644
--- a/extendr-api/src/wrapper/altrep.rs
+++ b/extendr-api/src/wrapper/altrep.rs
@@ -211,7 +211,7 @@ pub trait AltIntegerImpl: AltrepImpl {
             let num_elems = data.len().min(len - index);
             let dest = &mut data[0..num_elems];
             for (i, d) in dest.iter_mut().enumerate() {
-                *d = self.elt(i);
+                *d = self.elt(i + index);
             }
             num_elems
         }
@@ -292,7 +292,7 @@ pub trait AltRealImpl: AltrepImpl {
             let num_elems = data.len().min(len - index);
             let dest = &mut data[0..num_elems];
             for (i, d) in dest.iter_mut().enumerate() {
-                *d = self.elt(i);
+                *d = self.elt(i + index);
             }
             num_elems
         }
@@ -369,7 +369,7 @@ pub trait AltLogicalImpl: AltrepImpl {
             let num_elems = data.len().min(len - index);
             let dest = &mut data[0..num_elems];
             for (i, d) in dest.iter_mut().enumerate() {
-                *d = self.elt(i);
+                *d = self.elt(i + index);
             }
             num_elems
         }
@@ -410,7 +410,7 @@ pub trait AltRawImpl: AltrepImpl {
             let num_elems = data.len().min(len - index);
             let dest = &mut data[0..num_elems];
             for (i, d) in dest.iter_mut().enumerate() {
-                *d = self.elt(i);
+                *d = self.elt(i + index);
             }
             num_elems
         }
@@ -430,7 +430,7 @@ pub trait AltComplexImpl: AltrepImpl {
             let num_elems = data.len().min(len - index);
             let dest = &mut data[0..num_elems];
             for (i, d) in dest.iter_mut().enumerate() {
-                *d = self.elt(i);
+                *d = self.elt(i + index);
             }
             num_elems
         }
diff --git a/extendr-api/src/wrapper/list.rs b/extendr-api/src/wrapper/list.rs
index 110f38504..f57516758 100644
--- a/extendr-api/src/wrapper/list.rs
+++ b/extendr-api/src/wrapper/list.rs
@@ -1,3 +1,5 @@
+use std::iter::FromIterator;
+
 use super::*;
 
 #[derive(Debug, PartialEq, Clone)]
@@ -7,26 +9,26 @@ pub struct List {
 
 impl Default for List {
     fn default() -> Self {
-        List::new()
+        List::new(0)
     }
 }
 
 impl List {
-    /// Create a new, empty list.
+    /// Create a new list.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let list = List::new();
+    ///     let list = List::new(10);
     ///     assert_eq!(list.is_list(), true);
-    ///     assert_eq!(list.len(), 0);
+    ///     assert_eq!(list.len(), 10);
     /// }
     /// ```
-    pub fn new() -> List {
-        let values: &[Robj] = &[];
-        List::from_values(values)
+    pub fn new(size: usize) -> Self {
+        let robj = Robj::alloc_vector(VECSXP, size);
+        Self { robj }
     }
 
-    /// Wrapper for creating list (VECSXP) objects.
+    /// Wrapper for creating a list (VECSXP) object.
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
@@ -63,6 +65,49 @@ 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)
+    }
+
+    /// Build a list using separate names and values iterators.
+    /// Used internally by the list! macro.
+    pub fn from_names_and_values<N, V>(names: N, values: V) -> Result<Self>
+    where
+        N: IntoIterator,
+        N::IntoIter: ExactSizeIterator,
+        N::Item: ToVectorValue + AsRef<str>,
+        V: IntoIterator,
+        V::IntoIter: ExactSizeIterator,
+        V::Item: Into<Robj>,
+    {
+        let mut list = List::from_values(values);
+        list.set_names(names)?;
+        Ok(list)
+    }
+
     /// Return an iterator over the values of this list.
     /// ```
     /// use extendr_api::prelude::*;
@@ -80,15 +125,66 @@ impl List {
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let mut robj = list!(a=1, 2);
-    ///     let names_and_values : Vec<_> = robj.as_list().unwrap().iter().collect();
+    ///     let mut list = list!(a=1, 2);
+    ///     let names_and_values : Vec<_> = list.iter().collect();
     ///     assert_eq!(names_and_values, vec![("a", r!(1)), ("", r!(2))]);
     /// }
     /// ```
     pub fn iter(&self) -> NamedListIter {
+        // TODO: Make a proper NamedListIter.
         self.names()
             .map(|n| n.zip(self.values()))
-            .unwrap_or_else(|| StrIter::new().zip(ListIter::new()))
+            .unwrap_or_else(|| StrIter::new().zip(self.values()))
+    }
+
+    /// Get the list a slice of `Robj`s.
+    pub fn as_slice(&self) -> &[Robj] {
+        unsafe {
+            let data = DATAPTR(self.robj.get()) as *const Robj;
+            let len = self.robj.len();
+            std::slice::from_raw_parts(data, len)
+        }
+    }
+
+    /// Get a reference to an element in the list.
+    pub fn elt(&self, i: usize) -> Result<Robj> {
+        if i >= self.robj.len() {
+            Err(Error::OutOfRange(self.robj.clone()))
+        } else {
+            unsafe {
+                let sexp = VECTOR_ELT(self.robj.get(), i as R_xlen_t);
+                Ok(Robj::from_sexp(sexp))
+            }
+        }
+    }
+
+    /// Set an element in the list.
+    pub fn set_elt(&mut self, i: usize, value: Robj) -> Result<()> {
+        unsafe {
+            if i >= self.robj.len() {
+                Err(Error::OutOfRange(self.robj.clone()))
+            } else {
+                SET_VECTOR_ELT(self.robj.get(), i as R_xlen_t, value.get());
+                Ok(())
+            }
+        }
+    }
+
+    /// 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>>()
     }
 }
 
@@ -187,7 +283,7 @@ impl ExactSizeIterator for ListIter {
 /// ```
 /// use extendr_api::prelude::*;
 /// test! {
-///     let list = list!(1, 2);
+///     let list = Robj::from(list!(1, 2));
 ///     let vec : FromList<Vec<i32>> = list.try_into()?;
 ///     assert_eq!(vec.0, vec![1, 2]);
 /// }
@@ -205,7 +301,7 @@ where
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let list = list!(1, 2);
+    ///     let list = Robj::from(list!(1, 2));
     ///     let vec : FromList<Vec<i32>> = list.try_into()?;
     ///     assert_eq!(vec.0, vec![1, 2]);
     /// }
@@ -226,7 +322,7 @@ impl TryFrom<Robj> for ListIter {
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let list = list!(1, 2);
+    ///     let list = Robj::from(list!(1, 2));
     ///     let vec : ListIter = list.try_into()?;
     ///     assert_eq!(vec.collect::<Vec<_>>(), vec![r!(1), r!(2)]);
     /// }
@@ -242,8 +338,8 @@ impl From<ListIter> for Robj {
     /// ```
     /// use extendr_api::prelude::*;
     /// test! {
-    ///     let listiter = list!(1, 2).as_list().unwrap().values();
-    ///     assert_eq!(Robj::from(listiter), list!(1, 2));
+    ///     let listiter = list!(1, 2).values();
+    ///     assert_eq!(Robj::from(listiter), Robj::from(list!(1, 2)));
     /// }
     /// ```
     fn from(iter: ListIter) -> Self {
@@ -271,3 +367,24 @@ impl<T: AsRef<str>> KeyValue for (T, Robj) {
         self.1
     }
 }
+
+impl<T: Into<Robj>> FromIterator<T> for List {
+    /// Convert an iterator to a List object.
+    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
+        crate::single_threaded(|| unsafe {
+            let values: Vec<SEXP> = iter
+                .into_iter()
+                .map(|s| Rf_protect(s.into().get()))
+                .collect();
+
+            let len = values.len();
+            let robj = Robj::alloc_vector(VECSXP, len);
+            for (i, v) in values.into_iter().enumerate() {
+                SET_VECTOR_ELT(robj.get(), i as isize, v);
+            }
+            Rf_unprotect(len as i32);
+
+            List { robj }
+        })
+    }
+}
diff --git a/extendr-macros/src/lib.rs b/extendr-macros/src/lib.rs
index 2bea7b989..b7f5f5918 100644
--- a/extendr-macros/src/lib.rs
+++ b/extendr-macros/src/lib.rs
@@ -60,6 +60,7 @@ mod call;
 mod extendr_function;
 mod extendr_impl;
 mod extendr_module;
+mod list;
 mod pairlist;
 mod pairs;
 mod wrappers;
@@ -113,6 +114,15 @@ pub fn pairlist(item: TokenStream) -> TokenStream {
     pairlist::pairlist(item)
 }
 
+/// Create a List R object from a list of name-value pairs.
+/// ```ignore
+///     assert_eq!(list!(a=1, 2, 3), List::from_pairs(&[("a", 1), ("", 2), ("", 3)]));
+/// ```
+#[proc_macro]
+pub fn list(item: TokenStream) -> TokenStream {
+    list::list(item)
+}
+
 /// Call a function or primitive defined by a text expression with arbitrary parameters.
 /// This currently works by parsing and evaluating the string in R, but will probably acquire
 /// some shortcuts for simple expessions, for example by caching symbols and constant values.
diff --git a/extendr-macros/src/list.rs b/extendr-macros/src/list.rs
new file mode 100644
index 000000000..84de99f4a
--- /dev/null
+++ b/extendr-macros/src/list.rs
@@ -0,0 +1,32 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::parse_macro_input;
+
+use crate::pairs::Pairs;
+
+pub fn list(item: TokenStream) -> TokenStream {
+    let list = parse_macro_input!(item as Pairs);
+
+    let nv = list.names_and_values();
+
+    if nv.is_empty() {
+        TokenStream::from(quote!(List::default()))
+    } else {
+        let values: Vec<proc_macro2::TokenStream> = nv
+            .iter()
+            .map(|(_n, v)| quote!( extendr_api::Robj::from(#v) ))
+            .collect();
+        if nv.iter().any(|(n, _v)| !n.is_empty()) {
+            let names: Vec<proc_macro2::TokenStream> =
+                nv.iter().map(|(n, _v)| quote!( #n )).collect();
+            // Note that this unwrap should not fail.
+            TokenStream::from(quote!(
+                extendr_api::List::from_names_and_values(&[# ( #names ),*], &[# ( #values ),*]).unwrap()
+            ))
+        } else {
+            TokenStream::from(quote!(
+                extendr_api::List::from_values(&[# ( #values ),*])
+            ))
+        }
+    }
+}
diff --git a/extendr-macros/src/pairs.rs b/extendr-macros/src/pairs.rs
index 9e0fc2ec7..fa5bcb1cb 100644
--- a/extendr-macros/src/pairs.rs
+++ b/extendr-macros/src/pairs.rs
@@ -1,7 +1,9 @@
-use syn::{parse::ParseStream, punctuated::Punctuated, Expr, Token};
+//! Internal module for parsing R-like variadic arguments.
+
+use syn::{parse::ParseStream, punctuated::Punctuated, Expr, ExprAssign, ExprPath, Token};
 
 #[derive(Debug)]
-pub struct Pairs {
+pub(crate) struct Pairs {
     pub pairs: Punctuated<Expr, Token![,]>,
 }
 
@@ -21,3 +23,21 @@ impl syn::parse::Parse for Pairs {
         Ok(res)
     }
 }
+
+impl Pairs {
+    // Having parsed a variadic expression, extract the names and values.
+    pub(crate) fn names_and_values(&self) -> Vec<(String, &Expr)> {
+        self.pairs
+            .iter()
+            .map(|e| -> (String, &Expr) {
+                if let Expr::Assign(ExprAssign { left, right, .. }) = e {
+                    if let Expr::Path(ExprPath { path, .. }) = &**left {
+                        let s = path.get_ident().unwrap().to_string();
+                        return (s, right.as_ref());
+                    }
+                }
+                ("".to_owned(), e)
+            })
+            .collect()
+    }
+}


Test patch:
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 fb5285575..c897b885b 100644
--- a/extendr-api/tests/altrep_tests.rs
+++ b/extendr-api/tests/altrep_tests.rs
@@ -45,6 +45,11 @@ fn test_altinteger() {
         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());
+
+        // tests for get_region()
+        let mut dest = [0i32; 2];
+        integers_w_missing.get_region(2, &mut dest);
+        assert_eq!(dest, [2i32, 3]);
     }
 }
 
@@ -98,6 +103,11 @@ fn test_altreal() {
         if cfg!(not(target_arch = "x86")) {
             assert!(doubles_w_missing.elt(5).is_na());
         }
+
+        // tests for get_region()
+        let mut dest = [0f64; 2];
+        doubles_w_missing.get_region(2, &mut dest);
+        assert_eq!(dest, [2f64, 3f64]);
     }
 }
 
diff --git a/extendr-api/tests/extendr_macro_tests.rs b/extendr-api/tests/extendr_macro_tests.rs
new file mode 100644
index 000000000..96b819678
--- /dev/null
+++ b/extendr-api/tests/extendr_macro_tests.rs
@@ -0,0 +1,18 @@
+// use extendr_api::prelude::*;
+use extendr_api::{list, test, List, Result};
+
+#[test]
+fn test_list() {
+    test! {
+        let l : List = list!();
+        assert_eq!(l, List::default());
+        let l : List = list!(1);
+        assert_eq!(l, List::from_values([1]));
+        let l : List = list!(a=1);
+        assert_eq!(l, List::from_names_and_values(["a"], [1]).unwrap());
+        let l : List = list!(a=1, b=2);
+        assert_eq!(l, List::from_names_and_values(["a", "b"], [1, 2]).unwrap());
+
+        assert!(List::from_names_and_values(["a", "b"], [1, 2, 3]).is_err());
+    }
+}
diff --git a/extendr-api/tests/serializer_tests.rs b/extendr-api/tests/serializer_tests.rs
index 80b4ea0a8..d8a73482f 100644
--- a/extendr-api/tests/serializer_tests.rs
+++ b/extendr-api/tests/serializer_tests.rs
@@ -38,19 +38,19 @@ mod test {
 
             let u = E::Unit;
             let expected = list!(Unit=NULL);
-            assert_eq!(to_robj(&u).unwrap(), expected);
+            assert_eq!(to_robj(&u).unwrap(), r!(expected));
 
             let n = E::Newtype(1);
             let expected = list!(Newtype=1);
-            assert_eq!(to_robj(&n).unwrap(), expected);
+            assert_eq!(to_robj(&n).unwrap(), r!(expected));
 
             let t = E::Tuple(1, 2);
             let expected = list!(Tuple=list!(1, 2));
-            assert_eq!(to_robj(&t).unwrap(), expected);
+            assert_eq!(to_robj(&t).unwrap(), r!(expected));
 
             let s = E::Struct { a: 1 };
             let expected = list!(Struct=list!(a=1));
-            assert_eq!(to_robj(&s).unwrap(), expected);
+            assert_eq!(to_robj(&s).unwrap(), r!(expected));
         }
     }
 
diff --git a/extendr-api/tests/vector_tests.rs b/extendr-api/tests/vector_tests.rs
index f0b2de364..b43bfcfa0 100644
--- a/extendr-api/tests/vector_tests.rs
+++ b/extendr-api/tests/vector_tests.rs
@@ -34,3 +34,35 @@ fn test_strings() {
         assert_eq!(s.elt(0), "xyz");
     }
 }
+
+#[test]
+fn test_list() {
+    test! {
+        let s = List::new(10);
+        assert_eq!(s.len(), 10);
+        assert_eq!(s.rtype(), RType::List);
+
+        let mut s = List::from_values(["x", "y", "z"]);
+        assert_eq!(s.len(), 3);
+        assert_eq!(s.rtype(), RType::List);
+        assert_eq!(s.elt(0)?, r!("x"));
+        assert_eq!(s.elt(1)?, r!("y"));
+        assert_eq!(s.elt(2)?, r!("z"));
+        assert_eq!(s.elt(3).is_err(), true);
+
+        let v = s.as_slice().iter().map(|c| c).collect::<Vec<_>>();
+        assert_eq!(v, vec![&r!("x"), &r!("y"), &r!("z")]);
+
+        s.set_elt(1, r!("q"))?;
+        assert_eq!(s.elt(1)?, r!("q"));
+
+        let s : List = ["x", "y", "z"].iter().collect();
+        assert_eq!(s, list!("x", "y", "z"));
+
+        let v = list!(a="x", b="y", c="z").iter().collect::<Vec<_>>();
+        assert_eq!(v, vec![("a", r!("x")), ("b", r!("y")), ("c", r!("z"))]);
+
+        let s = List::from_values(["x", <&str>::na(), "z"]);
+        assert_eq!(s.elt(1)?.is_na(), true);
+    }
+}
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
 })