Repository: extendr/extendr
Pull request #335 (2021-12-08 16:15:35)

Allow renaming of R functions
This PR adds the ability to rename the R functions using the "r_name" attribute.

We also introduce the "mod_name" attribute which may be useful for specialisations
in the future.

Currently, this only works for non-impl functions.

```rust
#[extendr(
    use_try_from = true,
    r_name = "test.rename.rlike",
    mod_name = "test_rename_mymod"
)]
fn test_rename() {
}

extendr_module! {
    mod mymod;
    fn test_rename_mymod;
}
```


clauswilke: I think this one would also do well with an integration test.

Patch:
diff --git a/extendr-api/Cargo.toml b/extendr-api/Cargo.toml
index 08c61067b..79a3d89b8 100644
--- a/extendr-api/Cargo.toml
+++ b/extendr-api/Cargo.toml
@@ -22,12 +22,13 @@ ndarray = { version = "0.15.3", optional = true }
 lazy_static = "1.4"
 paste = "1.0.5"
 serde = { version = "1.0", features = ["derive"], optional = true }
+num-complex = { version = "0.4", optional = true }
 
 [features]
-default = ["serde"]
+default = []
 
 # All features to test
-tests-all = ["ndarray", "libR-sys/use-bindgen", "serde"]
+tests-all = ["ndarray", "libR-sys/use-bindgen", "serde", "num-complex"]
 
 # The minimal set of features without all optional ones
 tests-minimal = ["libR-sys/use-bindgen"]
diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
index 7f9d92650..947048f10 100644
--- a/extendr-api/src/lib.rs
+++ b/extendr-api/src/lib.rs
@@ -278,8 +278,6 @@ use scalar::Rbool;
 
 //////////////////////////////////////////////////
 
-pub struct Cplx(pub f64, pub f64);
-
 /// TRUE value eg. `r!(TRUE)`
 pub const TRUE: Rbool = Rbool::true_value();
 
@@ -346,13 +344,13 @@ pub unsafe fn register_call_methods(info: *mut libR_sys::DllInfo, metadata: Meta
     let mut rmethods = Vec::new();
     let mut cstrings = Vec::new();
     for func in metadata.functions {
-        let wrapped_name = format!("wrap__{}", func.name);
+        let wrapped_name = format!("wrap__{}", func.mod_name);
         make_method_def(&mut cstrings, &mut rmethods, &func, wrapped_name.as_str());
     }
 
     for imp in metadata.impls {
         for func in imp.methods {
-            let wrapped_name = format!("wrap__{}__{}", imp.name, func.name);
+            let wrapped_name = format!("wrap__{}__{}", imp.name, func.mod_name);
             make_method_def(&mut cstrings, &mut rmethods, &func, wrapped_name.as_str());
         }
     }
@@ -424,7 +422,7 @@ pub enum Rany<'a> {
     Logical(&'a Logicals),        // LGLSXP
     Integer(&'a Integers),        // INTSXP
     Real(&'a Doubles),            // REALSXP
-    Complex(&'a Robj),            // CPLXSXP
+    Complex(&'a Complexes),       // CPLXSXP
     String(&'a Robj),             // STRSXP
     Dot(&'a Robj),                // DOTSXP
     Any(&'a Robj),                // ANYSXP
@@ -851,9 +849,11 @@ mod tests {
             // Rust interface.
             let metadata = get_my_module_metadata();
             assert_eq!(metadata.functions[0].doc, " comment #1\n comment #2\n\n        comment #3\n        comment #4\n    *\n aux_func doc comment.");
-            assert_eq!(metadata.functions[0].name, "aux_func");
+            assert_eq!(metadata.functions[0].rust_name, "aux_func");
+            assert_eq!(metadata.functions[0].mod_name, "aux_func");
+            assert_eq!(metadata.functions[0].r_name, "aux_func");
             assert_eq!(metadata.functions[0].args[0].name, "_person");
-            assert_eq!(metadata.functions[1].name, "get_my_module_metadata");
+            assert_eq!(metadata.functions[1].rust_name, "get_my_module_metadata");
             assert_eq!(metadata.impls[0].name, "Person");
             assert_eq!(metadata.impls[0].methods.len(), 3);
 
diff --git a/extendr-api/src/metadata.rs b/extendr-api/src/metadata.rs
index f37914bd1..14aef6597 100644
--- a/extendr-api/src/metadata.rs
+++ b/extendr-api/src/metadata.rs
@@ -18,7 +18,9 @@ pub struct Arg {
 #[derive(Debug, PartialEq)]
 pub struct Func {
     pub doc: &'static str,
-    pub name: &'static str,
+    pub rust_name: &'static str,
+    pub mod_name: &'static str,
+    pub r_name: &'static str,
     pub args: Vec<Arg>,
     pub return_type: &'static str,
     pub func_ptr: *const u8,
@@ -54,13 +56,23 @@ impl From<Func> for Robj {
     fn from(val: Func) -> Self {
         List::from_values(&[
             r!(val.doc),
-            r!(val.name),
+            r!(val.rust_name),
+            r!(val.mod_name),
+            r!(val.r_name),
             r!(List::from_values(val.args)),
             r!(val.return_type),
             r!(val.hidden),
         ])
         .into_robj()
-        .set_names(&["doc", "name", "args", "return.type", "hidden"])
+        .set_names(&[
+            "doc",
+            "rust_name",
+            "mod_name",
+            "r_name",
+            "args",
+            "return.type",
+            "hidden",
+        ])
         .expect("From<Func> failed")
     }
 }
@@ -156,22 +168,22 @@ fn write_function_wrapper(
         write!(
             w,
             "{} <- function({}) invisible(.Call(",
-            sanitize_identifier(func.name),
+            sanitize_identifier(func.r_name),
             formal_args
         )?;
     } else {
         write!(
             w,
             "{} <- function({}) .Call(",
-            sanitize_identifier(func.name),
+            sanitize_identifier(func.r_name),
             formal_args
         )?;
     }
 
     if use_symbols {
-        write!(w, "wrap__{}", func.name)?;
+        write!(w, "wrap__{}", func.mod_name)?;
     } else {
-        write!(w, "\"wrap__{}\"", func.name)?;
+        write!(w, "\"wrap__{}\"", func.mod_name)?;
     }
 
     if !func.args.is_empty() {
@@ -230,7 +242,7 @@ fn write_method_wrapper(
             w,
             "{}${} <- function({}) invisible(.Call(",
             sanitize_identifier(class_name),
-            sanitize_identifier(func.name),
+            sanitize_identifier(func.r_name),
             formal_args
         )?;
     } else {
@@ -238,16 +250,16 @@ fn write_method_wrapper(
             w,
             "{}${} <- function({}) .Call(",
             sanitize_identifier(class_name),
-            sanitize_identifier(func.name),
+            sanitize_identifier(func.r_name),
             formal_args
         )?;
     }
 
     // Here no processing is needed because of `wrap__` prefix
     if use_symbols {
-        write!(w, "wrap__{}__{}", class_name, func.name)?;
+        write!(w, "wrap__{}__{}", class_name, func.mod_name)?;
     } else {
-        write!(w, "\"wrap__{}__{}\"", class_name, func.name)?;
+        write!(w, "\"wrap__{}__{}\"", class_name, func.mod_name)?;
     }
 
     if !actual_args.is_empty() {
diff --git a/extendr-api/src/prelude.rs b/extendr-api/src/prelude.rs
index 1b7c24a05..184f8d86a 100644
--- a/extendr-api/src/prelude.rs
+++ b/extendr-api/src/prelude.rs
@@ -36,8 +36,6 @@ pub use crate::{
     data_frame, factor, global, list, r, reprint, reprintln, rprint, rprintln, sym, test, var,
 };
 
-pub use super::Cplx;
-
 pub use super::wrapper::{
     AltComplexImpl, AltIntegerImpl, AltLogicalImpl, AltRawImpl, AltRealImpl, AltStringImpl, Altrep,
     AltrepImpl, RArray, RColumn, RMatrix, RMatrix3D,
@@ -57,8 +55,9 @@ pub use super::thread_safety::{
 };
 
 pub use super::wrapper::{
-    Doubles, EnvIter, Environment, Expression, ExternalPtr, FromList, Function, Integers, Language,
-    List, ListIter, Logicals, Nullable, Pairlist, Primitive, Promise, Raw, Rstr, Strings, Symbol,
+    Complexes, Doubles, EnvIter, Environment, Expression, ExternalPtr, FromList, Function,
+    Integers, Language, List, ListIter, Logicals, Nullable, Pairlist, Primitive, Promise, Raw,
+    Rstr, Strings, Symbol,
 };
 
 #[cfg(feature = "ndarray")]
diff --git a/extendr-api/src/robj/into_robj.rs b/extendr-api/src/robj/into_robj.rs
index be2485aed..9ede082c9 100644
--- a/extendr-api/src/robj/into_robj.rs
+++ b/extendr-api/src/robj/into_robj.rs
@@ -80,6 +80,13 @@ pub trait ToVectorValue {
         0.
     }
 
+    fn to_complex(&self) -> Rcomplex
+    where
+        Self: Sized,
+    {
+        Rcomplex { r: 0., i: 0. }
+    }
+
     fn to_integer(&self) -> i32
     where
         Self: Sized,
@@ -157,6 +164,34 @@ impl_real_tvv!(u32);
 impl_real_tvv!(u64);
 impl_real_tvv!(usize);
 
+macro_rules! impl_complex_tvv {
+    ($t: ty) => {
+        impl ToVectorValue for $t {
+            fn sexptype() -> SEXPTYPE {
+                CPLXSXP
+            }
+
+            fn to_complex(&self) -> Rcomplex {
+                unsafe { std::mem::transmute(*self) }
+            }
+        }
+
+        impl ToVectorValue for &$t {
+            fn sexptype() -> SEXPTYPE {
+                CPLXSXP
+            }
+
+            fn to_complex(&self) -> Rcomplex {
+                unsafe { std::mem::transmute(**self) }
+            }
+        }
+    };
+}
+
+impl_complex_tvv!(c64);
+impl_complex_tvv!(Rcplx);
+impl_complex_tvv!((f64, f64));
+
 macro_rules! impl_integer_tvv {
     ($t: ty) => {
         impl ToVectorValue for $t {
@@ -356,6 +391,12 @@ where
                         *ptr.add(i) = v.to_real();
                     }
                 }
+                CPLXSXP => {
+                    let ptr = COMPLEX(sexp);
+                    for (i, v) in iter.enumerate() {
+                        *ptr.add(i) = v.to_complex();
+                    }
+                }
                 INTSXP => {
                     let ptr = INTEGER(sexp);
                     for (i, v) in iter.enumerate() {
diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
index 69490d126..cac300054 100644
--- a/extendr-api/src/robj/mod.rs
+++ b/extendr-api/src/robj/mod.rs
@@ -10,6 +10,7 @@
 //!
 
 use libR_sys::*;
+use prelude::{c64, Rcplx};
 use std::os::raw;
 
 use crate::*;
@@ -336,6 +337,7 @@ impl Robj {
                     INTSXP => *(INTEGER(sexp)) == libR_sys::R_NaInt,
                     LGLSXP => *(LOGICAL(sexp)) == libR_sys::R_NaInt,
                     REALSXP => R_IsNA(*(REAL(sexp))) != 0,
+                    CPLXSXP => R_IsNA((*COMPLEX(sexp)).r) != 0,
                     _ => false,
                 }
             }
@@ -775,6 +777,9 @@ 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);
+make_typed_slice!(c64, COMPLEX, CPLXSXP);
+make_typed_slice!(Rcplx, COMPLEX, CPLXSXP);
+make_typed_slice!(Rcomplex, COMPLEX, CPLXSXP);
 
 /// These are helper functions which give access to common properties of R objects.
 #[allow(non_snake_case)]
diff --git a/extendr-api/src/scalar/mod.rs b/extendr-api/src/scalar/mod.rs
index 451b1dbc5..2d46f0cb2 100644
--- a/extendr-api/src/scalar/mod.rs
+++ b/extendr-api/src/scalar/mod.rs
@@ -5,3 +5,15 @@ mod rint;
 pub use rbool::Rbool;
 pub use rfloat::Rfloat;
 pub use rint::Rint;
+
+#[cfg(feature = "num-complex")]
+mod rcplx_full;
+
+#[cfg(feature = "num-complex")]
+pub use rcplx_full::{c64, Rcplx};
+
+#[cfg(not(feature = "num-complex"))]
+mod rcplx_default;
+
+#[cfg(not(feature = "num-complex"))]
+pub use rcplx_default::{c64, Rcplx};
diff --git a/extendr-api/src/scalar/rcplx_default.rs b/extendr-api/src/scalar/rcplx_default.rs
new file mode 100644
index 000000000..05e094806
--- /dev/null
+++ b/extendr-api/src/scalar/rcplx_default.rs
@@ -0,0 +1,156 @@
+use crate::scalar::Rfloat;
+use crate::*;
+use std::convert::TryFrom;
+
+#[allow(non_camel_case_types)]
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
+pub struct c64 {
+    re: f64,
+    im: f64,
+}
+
+impl c64 {
+    pub fn new(re: f64, im: f64) -> Self {
+        Self { re, im }
+    }
+}
+
+impl From<f64> for c64 {
+    fn from(val: f64) -> Self {
+        c64::new(val, 0.0)
+    }
+}
+
+impl std::fmt::Display for c64 {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "({}, {})", self.re, self.im)
+    }
+}
+
+impl CanBeNA for c64 {
+    fn is_na(&self) -> bool {
+        unsafe { R_IsNA(self.re) != 0 }
+    }
+
+    fn na() -> c64 {
+        unsafe { c64::new(R_NaReal, R_NaReal) }
+    }
+}
+
+/// Rcplx is a wrapper for f64 in the context of an R's complex vector.
+///
+/// Rcplx has a special NA value, obtained from R headers via R_NaReal.
+///
+/// Rcplx has the same footprint as R's complex value allowing us to use it in zero copy slices.
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
+pub struct Rcplx(c64);
+
+impl Rcplx {
+    // gen_impl!(Rcplx, c64);
+
+    pub fn re(&self) -> Rfloat {
+        Rfloat::from(self.0.re)
+    }
+
+    pub fn im(&self) -> Rfloat {
+        Rfloat::from(self.0.im)
+    }
+}
+
+impl CanBeNA for Rcplx {
+    fn is_na(&self) -> bool {
+        unsafe { R_IsNA(self.0.re) != 0 }
+    }
+
+    fn na() -> Rcplx {
+        unsafe { Rcplx::from(c64::new(R_NaReal, R_NaReal)) }
+    }
+}
+
+impl From<c64> for Rcplx {
+    fn from(val: c64) -> Self {
+        Rcplx(val)
+    }
+}
+
+impl From<f64> for Rcplx {
+    fn from(val: f64) -> Self {
+        Rcplx(c64::from(val))
+    }
+}
+
+impl From<(f64, f64)> for Rcplx {
+    fn from(val: (f64, f64)) -> Self {
+        Rcplx(c64::new(val.0, val.1))
+    }
+}
+
+impl From<(Rfloat, Rfloat)> for Rcplx {
+    fn from(val: (Rfloat, Rfloat)) -> Self {
+        Rcplx(c64::new(val.0.inner(), val.1.inner()))
+    }
+}
+
+impl From<Rfloat> for Rcplx {
+    fn from(val: Rfloat) -> Self {
+        Rcplx(c64::from(val.inner()))
+    }
+}
+
+impl From<Rcomplex> for Rcplx {
+    fn from(val: Rcomplex) -> Self {
+        Rcplx(c64::new(val.r, val.i))
+    }
+}
+
+impl From<Rcplx> for Option<c64> {
+    fn from(val: Rcplx) -> Self {
+        if val.is_na() {
+            None
+        } else {
+            Some(c64::new(val.re().inner(), val.im().inner()))
+        }
+    }
+}
+
+impl TryFrom<Robj> for Rcplx {
+    type Error = Error;
+
+    fn try_from(robj: Robj) -> Result<Self> {
+        // Check if the value is a scalar
+        match robj.len() {
+            0 => return Err(Error::ExpectedNonZeroLength(robj)),
+            1 => {}
+            _ => return Err(Error::ExpectedScalar(robj)),
+        };
+
+        // Check if the value is not a missing value.
+        if robj.is_na() {
+            return Ok(Rcplx::na());
+        }
+
+        // This should always work, NA is handled above.
+        if let Some(v) = robj.as_real() {
+            return Ok(Rcplx::from(v));
+        }
+
+        // Any integer (32 bit) can be represented as f64,
+        // this always works.
+        if let Some(v) = robj.as_integer() {
+            return Ok(Rcplx::from(v as f64));
+        }
+
+        // Complex slices return their first element.
+        if let Some(s) = robj.as_typed_slice() {
+            return Ok(s[0]);
+        }
+
+        Err(Error::ExpectedComplex(robj))
+    }
+}
+
+impl PartialEq<f64> for Rcplx {
+    fn eq(&self, other: &f64) -> bool {
+        self.re().inner() == *other && self.im() == 0.0
+    }
+}
diff --git a/extendr-api/src/scalar/rcplx_full.rs b/extendr-api/src/scalar/rcplx_full.rs
new file mode 100644
index 000000000..b54ae5bc7
--- /dev/null
+++ b/extendr-api/src/scalar/rcplx_full.rs
@@ -0,0 +1,196 @@
+use crate::scalar::macros::*;
+use crate::scalar::Rfloat;
+use crate::*;
+use std::convert::TryFrom;
+use std::ops::{Add, Div, Mul, Neg, Sub};
+use std::ops::{AddAssign, DivAssign, MulAssign, SubAssign};
+
+#[allow(non_camel_case_types)]
+pub type c64 = num_complex::Complex<f64>;
+
+impl CanBeNA for c64 {
+    fn is_na(&self) -> bool {
+        unsafe { R_IsNA(self.re) != 0 }
+    }
+
+    fn na() -> c64 {
+        unsafe { c64::new(R_NaReal, R_NaReal) }
+    }
+}
+
+/// Rcplx is a wrapper for f64 in the context of an R's complex vector.
+///
+/// Rcplx has a special NA value, obtained from R headers via R_NaReal.
+///
+/// Rcplx has the same footprint as R's complex value allowing us to use it in zero copy slices.
+pub struct Rcplx(c64);
+
+impl Rcplx {
+    gen_impl!(Rcplx, c64);
+
+    pub fn is_nan(&self) -> bool {
+        self.0.is_nan()
+    }
+
+    pub fn is_infinite(&self) -> bool {
+        self.0.is_infinite()
+    }
+
+    pub fn re(&self) -> Rfloat {
+        Rfloat::from(self.0.re)
+    }
+
+    pub fn im(&self) -> Rfloat {
+        Rfloat::from(self.0.im)
+    }
+}
+
+impl From<f64> for Rcplx {
+    fn from(val: f64) -> Self {
+        Rcplx(c64::from(val))
+    }
+}
+
+impl From<(f64, f64)> for Rcplx {
+    fn from(val: (f64, f64)) -> Self {
+        Rcplx(c64::new(val.0, val.1))
+    }
+}
+
+impl From<(Rfloat, Rfloat)> for Rcplx {
+    fn from(val: (Rfloat, Rfloat)) -> Self {
+        Rcplx(c64::new(val.0.inner(), val.1.inner()))
+    }
+}
+
+impl From<Rfloat> for Rcplx {
+    fn from(val: Rfloat) -> Self {
+        Rcplx(c64::from(val.inner()))
+    }
+}
+
+impl From<Rcomplex> for Rcplx {
+    fn from(val: Rcomplex) -> Self {
+        Rcplx(c64::new(val.r, val.i))
+    }
+}
+
+impl From<Rcplx> for Option<c64> {
+    fn from(val: Rcplx) -> Self {
+        if val.is_na() {
+            None
+        } else {
+            Some(c64::new(val.re().inner(), val.im().inner()))
+        }
+    }
+}
+
+// `NA_real_` is a `NaN` with specific bit representation.
+// Check that underlying `f64` is `NA_real_`.
+gen_trait_impl!(Rcplx, c64, |x: &Rcplx| x.inner().re.is_na(), c64::na());
+gen_from_primitive!(Rcplx, c64);
+// gen_from_scalar!(Rcplx, c64);
+gen_sum_iter!(Rcplx, 0f64);
+
+// Generate binary ops for +, -, * and /
+gen_binop!(
+    Rcplx,
+    c64,
+    Add,
+    |lhs: c64, rhs: c64| Some(lhs + rhs),
+    "Add two Rcplx values or an option of c64."
+);
+gen_binop!(
+    Rcplx,
+    c64,
+    Sub,
+    |lhs: c64, rhs: c64| Some(lhs - rhs),
+    "Subtract two Rcplx values or an option of c64."
+);
+gen_binop!(
+    Rcplx,
+    c64,
+    Mul,
+    |lhs: c64, rhs: c64| Some(lhs * rhs),
+    "Multiply two Rcplx values or an option of c64."
+);
+gen_binop!(
+    Rcplx,
+    c64,
+    Div,
+    |lhs: c64, rhs: c64| Some(lhs / rhs),
+    "Divide two Rcplx values or an option of c64."
+);
+gen_binopassign!(
+    Rcplx,
+    c64,
+    AddAssign,
+    |lhs: c64, rhs: c64| Some(lhs + rhs),
+    "Add two Rcplx values or an option of c64, modifying the left-hand side in place. Overflows to NA."
+);
+gen_binopassign!(
+    Rcplx,
+    c64,
+    SubAssign,
+    |lhs: c64, rhs: c64| Some(lhs - rhs),
+    "Subtract two Rcplx values or an option of c64, modifying the left-hand side in place. Overflows to NA."
+);
+gen_binopassign!(
+    Rcplx,
+    c64,
+    MulAssign,
+    |lhs: c64, rhs: c64| Some(lhs * rhs),
+    "Multiply two Rcplx values or an option of c64, modifying the left-hand side in place. Overflows to NA."
+);
+gen_binopassign!(
+    Rcplx,
+    c64,
+    DivAssign,
+    |lhs: c64, rhs: c64| Some(lhs / rhs),
+    "Divide two Rcplx values or an option of c64, modifying the left-hand side in place. Overflows to NA."
+);
+
+// Generate unary ops for -, !
+gen_unop!(Rcplx, Neg, |lhs: c64| Some(-lhs), "Negate a Rcplx value.");
+
+impl TryFrom<Robj> for Rcplx {
+    type Error = Error;
+
+    fn try_from(robj: Robj) -> Result<Self> {
+        // Check if the value is a scalar
+        match robj.len() {
+            0 => return Err(Error::ExpectedNonZeroLength(robj)),
+            1 => {}
+            _ => return Err(Error::ExpectedScalar(robj)),
+        };
+
+        // Check if the value is not a missing value.
+        if robj.is_na() {
+            return Ok(Rcplx::na());
+        }
+
+        // This should always work, NA is handled above.
+        if let Some(v) = robj.as_real() {
+            return Ok(Rcplx::from(v));
+        }
+
+        // Any integer (32 bit) can be represented as f64,
+        // this always works.
+        if let Some(v) = robj.as_integer() {
+            return Ok(Rcplx::from(v as f64));
+        }
+
+        // Complex slices return their first element.
+        if let Some(s) = robj.as_typed_slice() {
+            return Ok(s[0]);
+        }
+
+        Err(Error::ExpectedComplex(robj))
+    }
+}
+
+impl PartialEq<f64> for Rcplx {
+    fn eq(&self, other: &f64) -> bool {
+        self.re().inner() == *other && self.im() == 0.0
+    }
+}
diff --git a/extendr-api/src/wrapper/altrep.rs b/extendr-api/src/wrapper/altrep.rs
index a2074c096..ae6796a9a 100644
--- a/extendr-api/src/wrapper/altrep.rs
+++ b/extendr-api/src/wrapper/altrep.rs
@@ -1,4 +1,4 @@
-use prelude::{Rbool, Rfloat, Rint};
+use prelude::{Rbool, Rcplx, Rfloat, Rint};
 
 use super::*;
 
@@ -419,10 +419,10 @@ pub trait AltRawImpl: AltrepImpl {
 
 pub trait AltComplexImpl: AltrepImpl {
     /// Get a single element from this vector.
-    fn elt(&self, _index: usize) -> Cplx;
+    fn elt(&self, _index: usize) -> Rcplx;
 
     /// Get a multiple elements from this vector.
-    fn get_region(&self, index: usize, data: &mut [Cplx]) -> usize {
+    fn get_region(&self, index: usize, data: &mut [Rcplx]) -> usize {
         let len = self.length();
         if index > len {
             0
@@ -959,7 +959,7 @@ impl Altrep {
                 n: R_xlen_t,
                 buf: *mut Rcomplex,
             ) -> R_xlen_t {
-                let slice = std::slice::from_raw_parts_mut(buf as *mut Cplx, n as usize);
+                let slice = std::slice::from_raw_parts_mut(buf as *mut Rcplx, n as usize);
                 Altrep::get_state::<StateType>(x).get_region(i as usize, slice) as R_xlen_t
             }
 
@@ -1044,6 +1044,13 @@ impl Altrep {
         Rfloat,
         f64
     );
+    make_from_iterator!(
+        make_altcomplex_from_iterator,
+        make_altcomplex_class,
+        AltComplexImpl,
+        Rcplx,
+        c64
+    );
 }
 
 impl<Iter: ExactSizeIterator + std::fmt::Debug + Clone> AltrepImpl for Iter {
diff --git a/extendr-api/src/wrapper/complexes.rs b/extendr-api/src/wrapper/complexes.rs
new file mode 100644
index 000000000..9b677a530
--- /dev/null
+++ b/extendr-api/src/wrapper/complexes.rs
@@ -0,0 +1,70 @@
+use super::scalar::{c64, Rcplx};
+use super::*;
+use std::iter::FromIterator;
+
+/// An obscure `NA`-aware wrapper for R's complex vectors.
+/// Can be used to iterate over vectors obtained from R
+/// or to create new vectors that can be returned back to R.
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///     let mut vec = (0..5).map(|i| (i as f64).into()).collect::<Complexes>();
+///     assert_eq!(vec.len(), 5);
+/// }
+/// ```  
+#[derive(Debug, PartialEq, Clone)]
+pub struct Complexes {
+    pub(crate) robj: Robj,
+}
+
+crate::wrapper::macros::gen_vector_wrapper_impl!(
+    vector_type: Complexes,
+    scalar_type: Rcplx,
+    primitive_type: c64,
+    r_prefix: COMPLEX,
+    SEXP: CPLXSXP,
+    doc_name: complex,
+    altrep_constructor: make_altcomplex_from_iterator,
+);
+
+impl Complexes {
+    /// Get a region of elements from the vector.
+    pub fn get_region(&self, index: usize, dest: &mut [Rcplx]) -> usize {
+        unsafe {
+            let ptr: *mut Rcomplex = dest.as_mut_ptr() as *mut Rcomplex;
+            COMPLEX_GET_REGION(self.get(), index as R_xlen_t, dest.len() as R_xlen_t, ptr) as usize
+        }
+    }
+}
+
+// There is no SET_COMPLEX_ELT
+//
+// impl Complexes {
+//     pub fn set_elt(&mut self, index: usize, val: Rcplx) {
+//         unsafe {
+//             SET_COMPLEX_ELT(self.get(), index as R_xlen_t, val.inner());
+//         }
+//     }
+// }
+
+impl Deref for Complexes {
+    type Target = [Rcplx];
+
+    /// Treat Complexes as if it is a slice, like Vec<Rcplx>
+    fn deref(&self) -> &Self::Target {
+        unsafe {
+            let ptr = DATAPTR_RO(self.get()) as *const Rcplx;
+            std::slice::from_raw_parts(ptr, self.len())
+        }
+    }
+}
+
+impl DerefMut for Complexes {
+    /// Treat Complexes as if it is a mutable slice, like Vec<Rcplx>
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        unsafe {
+            let ptr = DATAPTR(self.get()) as *mut Rcplx;
+            std::slice::from_raw_parts_mut(ptr, self.len())
+        }
+    }
+}
diff --git a/extendr-api/src/wrapper/doubles.rs b/extendr-api/src/wrapper/doubles.rs
index e9440293b..c8117fce0 100644
--- a/extendr-api/src/wrapper/doubles.rs
+++ b/extendr-api/src/wrapper/doubles.rs
@@ -30,67 +30,23 @@ crate::wrapper::macros::gen_vector_wrapper_impl!(
     altrep_constructor: make_altreal_from_iterator,
 );
 
-#[cfg(test)]
-mod tests {
-    use crate::prelude::*;
-
-    #[test]
-    fn from_iterator() {
-        test! {
-            let vec : Doubles = (0..3).map(|i| (i as f64).into()).collect();
-            assert_eq!(vec, Doubles::from_values([0.0, 1.0, 2.0]));
-        }
-    }
-    #[test]
-    fn iter_mut() {
-        test! {
-            let mut vec = Doubles::from_values([0.0, 1.0, 2.0, 3.0]);
-            vec.iter_mut().for_each(|v| *v = *v + 1.0);
-            assert_eq!(vec, Doubles::from_values([1.0, 2.0, 3.0, 4.0]));
-        }
-    }
-
-    #[test]
-    fn iter() {
-        test! {
-            let vec = Doubles::from_values([0.0, 1.0, 2.0, 3.0]);
-            assert_eq!(vec.iter().sum::<Rfloat>(), 6.0);
+impl Doubles {
+    /// Get a region of elements from the vector.
+    pub fn get_region(&self, index: usize, dest: &mut [Rfloat]) -> usize {
+        unsafe {
+            let ptr: *mut f64 = dest.as_mut_ptr() as *mut f64;
+            REAL_GET_REGION(self.get(), index as R_xlen_t, dest.len() as R_xlen_t, ptr) as usize
         }
     }
 
-    #[test]
-    fn from_values_short() {
-        test! {
-            // Short (<64k) vectors are allocated.
-            let vec = Doubles::from_values((0..3).map(|i| 2.0 - i as f64));
-            assert_eq!(vec.is_altrep(), false);
-            assert_eq!(r!(vec.clone()), r!([2.0, 1.0, 0.0]));
-            assert_eq!(vec.elt(1), 1.0);
-            let mut dest = [0.0.into(); 2];
-            vec.get_region(1, &mut dest);
-            assert_eq!(dest, [1.0, 0.0]);
-        }
-    }
-    #[test]
-    fn from_values_long() {
-        test! {
-            // Long (>=64k) vectors are lazy ALTREP objects.
-            let vec = Doubles::from_values((0..1000000000).map(|x| x as f64));
-            assert_eq!(vec.is_altrep(), true);
-            assert_eq!(vec.elt(12345678), 12345678.0);
-            let mut dest = [0.0.into(); 2];
-            vec.get_region(12345678, &mut dest);
-            assert_eq!(dest, [12345678.0, 12345679.0]);
-        }
+    /// Return `TRUE` if the vector is sorted, `FALSE` if not, or `NA_BOOL` if unknown.
+    pub fn is_sorted(&self) -> Rbool {
+        unsafe { REAL_IS_SORTED(self.get()).into() }
     }
 
-    #[test]
-    fn new() {
-        test! {
-            let vec = Doubles::new(10);
-            assert_eq!(vec.is_real(), true);
-            assert_eq!(vec.len(), 10);
-        }
+    /// Return `TRUE` if the vector has no `NA`s, `FALSE` if any, or `NA_BOOL` if unknown.
+    pub fn no_na(&self) -> Rbool {
+        unsafe { REAL_NO_NA(self.get()).into() }
     }
 }
 
diff --git a/extendr-api/src/wrapper/integers.rs b/extendr-api/src/wrapper/integers.rs
index b86080ebf..7b8c3365c 100644
--- a/extendr-api/src/wrapper/integers.rs
+++ b/extendr-api/src/wrapper/integers.rs
@@ -30,6 +30,26 @@ crate::wrapper::macros::gen_vector_wrapper_impl!(
     altrep_constructor: make_altinteger_from_iterator,
 );
 
+impl Integers {
+    /// Get a region of elements from the vector.
+    pub fn get_region(&self, index: usize, dest: &mut [Rint]) -> usize {
+        unsafe {
+            let ptr: *mut i32 = dest.as_mut_ptr() as *mut i32;
+            INTEGER_GET_REGION(self.get(), index as R_xlen_t, dest.len() as R_xlen_t, ptr) as usize
+        }
+    }
+
+    /// Return `TRUE` if the vector is sorted, `FALSE` if not, or `NA_BOOL` if unknown.
+    pub fn is_sorted(&self) -> Rbool {
+        unsafe { INTEGER_IS_SORTED(self.get()).into() }
+    }
+
+    /// Return `TRUE` if the vector has no `NA`s, `FALSE` if any, or `NA_BOOL` if unknown.
+    pub fn no_na(&self) -> Rbool {
+        unsafe { INTEGER_NO_NA(self.get()).into() }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use crate::prelude::*;
diff --git a/extendr-api/src/wrapper/logicals.rs b/extendr-api/src/wrapper/logicals.rs
index 5d4c92c6f..278b004ad 100644
--- a/extendr-api/src/wrapper/logicals.rs
+++ b/extendr-api/src/wrapper/logicals.rs
@@ -31,6 +31,16 @@ crate::wrapper::macros::gen_vector_wrapper_impl!(
     altrep_constructor: make_altlogical_from_iterator,
 );
 
+impl Logicals {
+    /// Get a region of elements from the vector.
+    pub fn get_region(&self, index: usize, dest: &mut [Rbool]) -> usize {
+        unsafe {
+            let ptr: *mut i32 = dest.as_mut_ptr() as *mut i32;
+            LOGICAL_GET_REGION(self.get(), index as R_xlen_t, dest.len() as R_xlen_t, ptr) as usize
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use crate::prelude::*;
diff --git a/extendr-api/src/wrapper/macros.rs b/extendr-api/src/wrapper/macros.rs
index 1a9cafef7..7095f4cbc 100644
--- a/extendr-api/src/wrapper/macros.rs
+++ b/extendr-api/src/wrapper/macros.rs
@@ -91,24 +91,6 @@ macro_rules! gen_vector_wrapper_impl {
                 }
             }
 
-            /// Get a region of elements from the vector.
-            pub fn get_region(&self, index: usize, dest: &mut [$scalar_type]) -> usize {
-                unsafe {
-                    let ptr : *mut $primitive_type = dest.as_mut_ptr() as *mut $primitive_type;
-                    paste::paste!{ [<$r_prefix _GET_REGION>](self.get(), index as R_xlen_t, dest.len() as R_xlen_t, ptr) as usize }
-                }
-            }
-
-            /// Return `TRUE` if the vector is sorted, `FALSE` if not, or `NA_BOOL` if unknown.
-            pub fn is_sorted(&self) -> Rbool {
-                unsafe { paste::paste!{ [<$r_prefix _IS_SORTED>](self.get()).into() } }
-            }
-
-            /// Return `TRUE` if the vector has no `NA`s, `FALSE` if any, or `NA_BOOL` if unknown.
-            pub fn no_na(&self) -> Rbool {
-                unsafe { paste::paste!{ [<$r_prefix _NO_NA>](self.get()).into() } }
-            }
-
             paste::paste!{
                 #[doc = "Return an iterator for a " $doc_name " object."]
                 #[doc = "Forces ALTREP objects to manifest."]
diff --git a/extendr-api/src/wrapper/mod.rs b/extendr-api/src/wrapper/mod.rs
index f4ec8bd60..9a1481134 100644
--- a/extendr-api/src/wrapper/mod.rs
+++ b/extendr-api/src/wrapper/mod.rs
@@ -6,6 +6,7 @@ use crate::*;
 use libR_sys::*;
 
 pub mod altrep;
+pub mod complexes;
 pub mod doubles;
 pub mod environment;
 pub mod expr;
@@ -32,6 +33,7 @@ pub use altrep::{
     AltComplexImpl, AltIntegerImpl, AltLogicalImpl, AltRawImpl, AltRealImpl, AltStringImpl, Altrep,
     AltrepImpl,
 };
+pub use complexes::Complexes;
 pub use doubles::Doubles;
 pub use environment::{EnvIter, Environment};
 pub use expr::Expression;
@@ -197,6 +199,12 @@ make_conversions!(S4, ExpectedS4, is_s4, "Not a S4 type");
 make_conversions!(Integers, ExpectedInteger, is_integer, "Not an integer type");
 make_conversions!(Logicals, ExpectedLogical, is_logical, "Not a logical type");
 make_conversions!(Doubles, ExpectedReal, is_real, "Not a floating point type");
+make_conversions!(
+    Complexes,
+    ExpectedComplex,
+    is_complex,
+    "Not a complex number or vector"
+);
 // make_conversions!(Function, ExpectedFunction, is_function, "Not a function");
 
 make_conversions!(Strings, ExpectedString, is_string, "Not a string vector");
diff --git a/extendr-engine/src/lib.rs b/extendr-engine/src/lib.rs
index d841cca8b..72562ee4d 100644
--- a/extendr-engine/src/lib.rs
+++ b/extendr-engine/src/lib.rs
@@ -43,7 +43,7 @@ pub fn start_r() {
             // In case you are curious.
             // Maybe 8MB is a bit small.
             // eprintln!("R_CStackLimit={:016x}", R_CStackLimit);
-            R_CStackLimit = usize::max_value();
+            R_CStackLimit = usize::MAX;
 
             setup_Rmainloop();
         }
diff --git a/extendr-macros/src/extendr_function.rs b/extendr-macros/src/extendr_function.rs
index f3aecb6e8..25ba3e0ab 100644
--- a/extendr-macros/src/extendr_function.rs
+++ b/extendr-macros/src/extendr_function.rs
@@ -25,6 +25,10 @@ pub fn extendr_function(args: Vec<syn::NestedMeta>, mut func: ItemFn) -> TokenSt
 pub fn parse_options(opts: &mut wrappers::ExtendrOptions, arg: &syn::NestedMeta) {
     use syn::{Lit, LitBool, Meta, MetaNameValue, NestedMeta};
 
+    fn help_message() -> ! {
+        panic!("expected #[extendr(use_try_from=bool, r_name=\"name\")]");
+    }
+
     match arg {
         NestedMeta::Meta(Meta::NameValue(MetaNameValue {
             ref path,
@@ -34,11 +38,25 @@ pub fn parse_options(opts: &mut wrappers::ExtendrOptions, arg: &syn::NestedMeta)
             if path.is_ident("use_try_from") {
                 if let Lit::Bool(LitBool { value, .. }) = lit {
                     opts.use_try_from = *value;
+                } else {
+                    help_message();
+                }
+            } else if path.is_ident("r_name") {
+                if let Lit::Str(litstr) = lit {
+                    opts.r_name = Some(litstr.value());
+                } else {
+                    help_message();
+                }
+            } else if path.is_ident("mod_name") {
+                if let Lit::Str(litstr) = lit {
+                    opts.mod_name = Some(litstr.value());
+                } else {
+                    help_message();
                 }
             } else {
-                panic!("expected use_try_from");
+                help_message();
             }
         }
-        _ => panic!("expected #[extendr(opt = \"string\", ...)]"),
+        _ => help_message(),
     }
 }
diff --git a/extendr-macros/src/extendr_module.rs b/extendr-macros/src/extendr_module.rs
index c545f8f3e..a67a04e1f 100644
--- a/extendr-macros/src/extendr_module.rs
+++ b/extendr-macros/src/extendr_module.rs
@@ -54,7 +54,9 @@ pub fn extendr_module(item: TokenStream) -> TokenStream {
             // Add this function to the list, but set hidden: true.
             functions.push(extendr_api::metadata::Func {
                 doc: "Metadata access function.",
-                name: #module_metadata_name_string,
+                rust_name: #module_metadata_name_string,
+                mod_name: #module_metadata_name_string,
+                r_name: #module_metadata_name_string,
                 args: Vec::new(),
                 return_type: "Metadata",
                 func_ptr: #wrap_module_metadata_name as * const u8,
@@ -64,7 +66,9 @@ pub fn extendr_module(item: TokenStream) -> TokenStream {
             // Add this function to the list, but set hidden: true.
             functions.push(extendr_api::metadata::Func {
                 doc: "Wrapper generator.",
-                name: #make_module_wrappers_name_string,
+                rust_name: #make_module_wrappers_name_string,
+                mod_name: #make_module_wrappers_name_string,
+                r_name: #make_module_wrappers_name_string,
                 args: vec![
                     extendr_api::metadata::Arg { name: "use_symbols", arg_type: "bool", default: None },
                     extendr_api::metadata::Arg { name: "package_name", arg_type: "&str", default: None },
diff --git a/extendr-macros/src/wrappers.rs b/extendr-macros/src/wrappers.rs
index 87cc9c3bf..081e82946 100644
--- a/extendr-macros/src/wrappers.rs
+++ b/extendr-macros/src/wrappers.rs
@@ -7,6 +7,8 @@ pub const WRAP_PREFIX: &str = "wrap__";
 #[derive(Debug, Default)]
 pub struct ExtendrOptions {
     pub use_try_from: bool,
+    pub r_name: Option<String>,
+    pub mod_name: Option<String>,
 }
 
 // Generate wrappers for a specific function.
@@ -18,16 +20,29 @@ pub fn make_function_wrappers(
     sig: &mut syn::Signature,
     self_ty: Option<&syn::Type>,
 ) {
-    let func_name = &sig.ident;
+    let rust_name = sig.ident.clone();
 
-    let wrap_name = format_ident!("{}{}{}", WRAP_PREFIX, prefix, func_name);
-    let meta_name = format_ident!("{}{}{}", META_PREFIX, prefix, func_name);
+    let r_name_str = if let Some(r_name) = opts.r_name.as_ref() {
+        r_name.clone()
+    } else {
+        sig.ident.to_string()
+    };
+
+    let mod_name = if let Some(mod_name) = opts.mod_name.as_ref() {
+        format_ident!("{}", mod_name)
+    } else {
+        sig.ident.clone()
+    };
+
+    let wrap_name = format_ident!("{}{}{}", WRAP_PREFIX, prefix, mod_name);
+    let meta_name = format_ident!("{}{}{}", META_PREFIX, prefix, mod_name);
 
-    let name_str = format!("{}", func_name);
+    let rust_name_str = format!("{}", rust_name);
+    let c_name_str = format!("{}", mod_name);
     let doc_string = get_doc_string(attrs);
     let return_type_string = get_return_type(sig);
 
-    let panic_str = format!("{} panicked.\0", func_name);
+    let panic_str = format!("{} panicked.\0", r_name_str);
 
     let inputs = &mut sig.inputs;
     let has_self = matches!(inputs.iter().next(), Some(FnArg::Receiver(_)));
@@ -41,19 +56,19 @@ pub fn make_function_wrappers(
             // eg. Person::name(&mut self)
             quote! { extendr_api::unwrap_or_throw(
                 <&mut #self_ty>::from_robj(&_self_robj)
-            ).#func_name }
+            ).#rust_name }
         } else {
             // eg. Person::name(&self)
             quote! { extendr_api::unwrap_or_throw(
                 <&#self_ty>::from_robj(&_self_robj)
-            ).#func_name }
+            ).#rust_name }
         }
     } else if let Some(ref self_ty) = &self_ty {
         // eg. Person::new()
-        quote! { <#self_ty>::#func_name }
+        quote! { <#self_ty>::#rust_name }
     } else {
         // eg. aux_func()
-        quote! { #func_name }
+        quote! { #rust_name }
     };
 
     let formal_args: Punctuated<FnArg, Token![,]> = inputs
@@ -108,7 +123,9 @@ pub fn make_function_wrappers(
             ];
             metadata.push(extendr_api::metadata::Func {
                 doc: #doc_string,
-                name: #name_str,
+                rust_name: #rust_name_str,
+                r_name: #r_name_str,
+                mod_name: #c_name_str,
                 args: args,
                 return_type: #return_type_string,
                 func_ptr: #wrap_name as * const u8,


Test patch:
diff --git a/extendr-api/tests/altrep_tests.rs b/extendr-api/tests/altrep_tests.rs
index ea909fc31..58497c8d4 100644
--- a/extendr-api/tests/altrep_tests.rs
+++ b/extendr-api/tests/altrep_tests.rs
@@ -191,8 +191,8 @@ fn test_altcomplex() {
         }
 
         impl AltComplexImpl for MyCompactComplexRange {
-            fn elt(&self, index: usize) -> Cplx {
-                Cplx(self.start + self.step * index as f64, self.start + self.step * index as f64)
+            fn elt(&self, index: usize) -> Rcplx {
+                Rcplx::from(c64::new(self.start + self.step * index as f64, self.start + self.step * index as f64))
             }
         }
 
diff --git a/extendr-api/tests/extendr_macro.rs b/extendr-api/tests/extendr_macro.rs
index 2ca30b9bd..a6bfbab17 100644
--- a/extendr-api/tests/extendr_macro.rs
+++ b/extendr-api/tests/extendr_macro.rs
@@ -47,6 +47,18 @@ fn test_integers(val: Integers) -> Integers {
     val
 }
 
+#[extendr(
+    use_try_from = true,
+    r_name = "test.rename.rlike",
+    mod_name = "test_rename_mymod"
+)]
+fn test_rename() {}
+
+extendr_module! {
+    mod mymod;
+    fn test_rename_mymod;
+}
+
 #[extendr(use_try_from = true)]
 fn test_integers2(val: Integers) -> Integers {
     val.iter().map(|i| i + 1).collect()
@@ -187,7 +199,9 @@ fn test_metadata() {
         funcs[0],
         Func {
             doc: "",
-            name: "test_metadata_1",
+            rust_name: "test_metadata_1",
+            mod_name: "test_metadata_1",
+            r_name: "test_metadata_1",
             args: args,
             return_type: "i32",
             func_ptr: wrap__test_metadata_1 as *const u8,
diff --git a/extendr-api/tests/extendr_macro_tests.rs b/extendr-api/tests/macro_tests.rs
similarity index 100%
rename from extendr-api/tests/extendr_macro_tests.rs
rename to extendr-api/tests/macro_tests.rs
diff --git a/extendr-api/tests/scalar_tests.rs b/extendr-api/tests/scalar_tests.rs
index 9d35ab82b..8956989af 100644
--- a/extendr-api/tests/scalar_tests.rs
+++ b/extendr-api/tests/scalar_tests.rs
@@ -232,6 +232,80 @@ fn test_rfloat() {
     }
 }
 
+#[test]
+#[cfg(feature = "num-complex")]
+fn test_rcplx() {
+    test! {
+        let a = Rcplx::from((20., 300.));
+        let b = Rcplx::from((10., 400.));
+        assert_eq!(a + b, Rcplx::from((30., 700.)));
+        assert_eq!(a - b, Rcplx::from((10., -100.)));
+        assert_eq!(a * b, Rcplx::from((-119800.0, 11000.0)));
+
+        let a = Rcplx::from(20.);
+        let b = Rcplx::from(10.);
+        assert_eq!(a / b, Rcplx::from(2.));
+        assert_eq!(-a, Rcplx::from(-20.));
+
+        assert_eq!(&a + b, Rcplx::from(30.));
+        assert_eq!(&a - b, Rcplx::from(10.));
+        assert_eq!(&a * b, Rcplx::from(200.));
+        assert_eq!(&a / b, Rcplx::from(2.));
+        assert_eq!(-&a, Rcplx::from(-20.));
+
+        // NA lhs
+        let a = Rcplx::na();
+        let b = Rcplx::from(10.);
+        assert!((a + b).is_na());
+        assert!((a - b).is_na());
+        assert!((a * b).is_na());
+        assert!((a / b).is_na());
+        assert!((-a).is_na());
+
+        // NA rhs
+        let a = Rcplx::from(10.);
+        let b = Rcplx::na();
+        assert!((a + b).is_na());
+        assert!((a - b).is_na());
+        assert!((a * b).is_na());
+        assert!((a / b).is_na());
+
+        // Inf is a single value, so can be tested for equality
+        let a = Rcplx::from(f64::INFINITY);
+        let b = Rcplx::from(42.);
+        assert_eq!(a + b, a);
+        assert_eq!(a - b, a);
+        assert_eq!(b - a, Rcplx::from(f64::NEG_INFINITY));
+        // assert_eq!(a * b, a);
+        // assert_eq!(a / b, a);
+        assert_eq!(-a, Rcplx::from(f64::NEG_INFINITY));
+
+        let a = Rcplx::from(f64::NEG_INFINITY);
+        assert_eq!(a + b, a);
+        assert_eq!(a - b, a);
+        assert_eq!(b - a, Rcplx::from(f64::INFINITY));
+        // assert_eq!(a * b, a);
+        // assert_eq!(a / b, a);
+        assert_eq!(-a, Rcplx::from(f64::INFINITY));
+
+        // Operations with NaN produce NaN
+        let a = Rcplx::from(f64::NAN);
+        assert!((a + b).is_nan());
+        assert!((a - b).is_nan());
+        assert!((a * b).is_nan());
+        assert!((a / b).is_nan());
+        assert!((-a).is_nan());
+
+        // Infinity
+        assert!(Rcplx::from(f64::INFINITY).is_infinite());
+        assert!(Rcplx::from(f64::NEG_INFINITY).is_infinite());
+        assert!(!Rcplx::from(0.).is_infinite());
+
+        // Some more, testing mixed binary operators
+        assert!((Rcplx::from(f64::INFINITY) + Rcplx::from(1.)).is_infinite());
+    }
+}
+
 #[test]
 fn test_rfloat_opassign() {
     test! {
diff --git a/extendr-api/tests/vector_tests.rs b/extendr-api/tests/vector_tests.rs
index 292c8e0c1..793aaec29 100644
--- a/extendr-api/tests/vector_tests.rs
+++ b/extendr-api/tests/vector_tests.rs
@@ -129,6 +129,56 @@ fn test_doubles() {
     }
 }
 
+#[test]
+fn test_complexes() {
+    test! {
+        let s = Complexes::new(10);
+        assert_eq!(s.len(), 10);
+        assert_eq!(s.rtype(), RType::Complex);
+
+        let s = Complexes::from_values([1.0, 2.0, 3.0]);
+        assert_eq!(s.len(), 3);
+        assert_eq!(s.rtype(), RType::Complex);
+        assert_eq!(s.elt(0), 1.0);
+        assert_eq!(s.elt(1), 2.0);
+        assert_eq!(s.elt(2), 3.0);
+        assert!(s.elt(3).is_na());
+
+        let v = s.iter().collect::<Vec<Rcplx>>();
+        assert_eq!(v, [1.0, 2.0, 3.0]);
+
+        // s.set_elt(1, 5.0.into());
+        // assert_eq!(s.elt(1), 5.0);
+
+        let s : Complexes = [1.0, 2.0, 3.0].iter().map(|i| Rcplx::from(*i)).collect();
+        let v = s.iter().collect::<Complexes>();
+        assert_eq!(v, Complexes::from_values([1.0, 2.0, 3.0]));
+
+        // Bug: from_values should be Into<Rcplx>
+        //let s = Complexes::from_values([Rint::from(1), Rint::na(), Rint::from(3)]);
+        //assert_eq!(s.elt(1).is_na(), true);
+
+        // let robj = r!([Rcplx::from(1.0), Rcplx::from(2.0), Rcplx::from(3.0)]);
+        let robj = r!([(1.0, 0.0), (2.0, 0.0), (3.0, 0.0)]);
+        let s = Complexes::try_from(robj)?;
+        assert_eq!(s.len(), 3);
+        assert_eq!(s.elt(0), 1.0);
+
+        // Test Deref and DerefMut.
+        let mut s = Complexes::from_values([1.0, 2.0, 3.0]);
+        assert_eq!(s[0], 1.0);
+        assert_eq!(s[1], 2.0);
+        assert_eq!(s[2], 3.0);
+
+        s[0] = 4.0.into();
+        s[1] = 5.0.into();
+        s[2] = 6.0.into();
+        assert_eq!(s[0], 4.0);
+        assert_eq!(s[1], 5.0);
+        assert_eq!(s[2], 6.0);
+    }
+}
+
 #[test]
 fn test_integers() {
     test! {
@@ -191,3 +241,126 @@ fn test_rstr() {
         assert_eq!(x, "xyz");
     }
 }
+
+#[test]
+fn test_doubles_from_iterator() {
+    test! {
+        let vec : Doubles = (0..3).map(|i| (i as f64).into()).collect();
+        assert_eq!(vec, Doubles::from_values([0.0, 1.0, 2.0]));
+    }
+}
+#[test]
+fn test_doubles_iter_mut() {
+    test! {
+        let mut vec = Doubles::from_values([0.0, 1.0, 2.0, 3.0]);
+        vec.iter_mut().for_each(|v| *v = *v + 1.0);
+        assert_eq!(vec, Doubles::from_values([1.0, 2.0, 3.0, 4.0]));
+    }
+}
+
+#[test]
+fn test_doubles_iter() {
+    test! {
+        let vec = Doubles::from_values([0.0, 1.0, 2.0, 3.0]);
+        assert_eq!(vec.iter().sum::<Rfloat>(), 6.0);
+    }
+}
+
+#[test]
+fn test_doubles_from_values_short() {
+    test! {
+        // Short (<64k) vectors are allocated.
+        let vec = Doubles::from_values((0..3).map(|i| 2.0 - i as f64));
+        assert_eq!(vec.is_altrep(), false);
+        assert_eq!(r!(vec.clone()), r!([2.0, 1.0, 0.0]));
+        assert_eq!(vec.elt(1), 1.0);
+        let mut dest = [0.0.into(); 2];
+        vec.get_region(1, &mut dest);
+        assert_eq!(dest, [1.0, 0.0]);
+    }
+}
+#[test]
+fn test_doubles_from_values_long() {
+    test! {
+        // Long (>=64k) vectors are lazy ALTREP objects.
+        let vec = Doubles::from_values((0..1000000000).map(|x| x as f64));
+        assert_eq!(vec.is_altrep(), true);
+        assert_eq!(vec.elt(12345678), 12345678.0);
+        let mut dest = [0.0.into(); 2];
+        vec.get_region(12345678, &mut dest);
+        assert_eq!(dest, [12345678.0, 12345679.0]);
+    }
+}
+
+#[test]
+fn test_doubles_new() {
+    test! {
+        let vec = Doubles::new(10);
+        assert_eq!(vec.is_real(), true);
+        assert_eq!(vec.len(), 10);
+    }
+}
+
+#[cfg(feature = "num-complex")]
+mod num_complex {
+    use extendr_api::prelude::*;
+
+    #[test]
+    fn from_iterator() {
+        test! {
+            let vec : Complexes = (0..3).map(|i| (i as f64).into()).collect();
+            assert_eq!(vec, Complexes::from_values([0.0, 1.0, 2.0]));
+        }
+    }
+    #[test]
+    fn iter_mut() {
+        test! {
+            let mut vec = Complexes::from_values([0.0, 1.0, 2.0, 3.0]);
+            vec.iter_mut().for_each(|v| *v = *v + Rcplx::from(1.0));
+            assert_eq!(vec, Complexes::from_values([1.0, 2.0, 3.0, 4.0]));
+        }
+    }
+
+    #[test]
+    fn iter() {
+        test! {
+            let vec = Complexes::from_values([0.0, 1.0, 2.0, 3.0]);
+            assert_eq!(vec.iter().sum::<Rcplx>(), Rcplx::from(6.0));
+        }
+    }
+
+    #[test]
+    fn from_values_short() {
+        test! {
+            // Short (<64k) vectors are allocated.
+            let vec = Complexes::from_values((0..3).map(|i| 2.0 - i as f64));
+            assert_eq!(vec.is_altrep(), false);
+            assert_eq!(r!(vec.clone()), r!([Rcplx::from(2.0), Rcplx::from(1.0), Rcplx::from(0.0)]));
+            assert_eq!(vec.elt(1), Rcplx::from(1.0));
+            let mut dest = [0.0.into(); 2];
+            vec.get_region(1, &mut dest);
+            assert_eq!(dest, [Rcplx::from(1.0), Rcplx::from(0.0)]);
+        }
+    }
+    #[test]
+    fn from_values_long() {
+        test! {
+            // Long (>=64k) vectors are lazy ALTREP objects.
+            let vec = Complexes::from_values((0..1000000000).map(|x| x as f64));
+            assert_eq!(vec.is_altrep(), true);
+            assert_eq!(vec.elt(12345678), Rcplx::from(12345678.0));
+            let mut dest = [0.0.into(); 2];
+            vec.get_region(12345678, &mut dest);
+            assert_eq!(dest, [Rcplx::from(12345678.0), Rcplx::from(12345679.0)]);
+        }
+    }
+
+    #[test]
+    fn new() {
+        test! {
+            let vec = Complexes::new(10);
+            assert_eq!(vec.is_complex(), true);
+            assert_eq!(vec.len(), 10);
+        }
+    }
+}
diff --git a/tests/extendrtests/R/extendr-wrappers.R b/tests/extendrtests/R/extendr-wrappers.R
index df938957b..06fd92cef 100644
--- a/tests/extendrtests/R/extendr-wrappers.R
+++ b/tests/extendrtests/R/extendr-wrappers.R
@@ -34,6 +34,8 @@ get_logicals_element <- function(x, i) .Call(wrap__get_logicals_element, x, i)
 
 doubles_square <- function(input) .Call(wrap__doubles_square, input)
 
+complexes_square <- function(input) .Call(wrap__complexes_square, input)
+
 integers_square <- function(input) .Call(wrap__integers_square, input)
 
 logicals_not <- function(input) .Call(wrap__logicals_not, input)
@@ -60,6 +62,8 @@ special_param_names <- function(`_x`, `_y`) .Call(wrap__special_param_names, `_x
 #' @export
 `__00__special_function_name` <- function() invisible(.Call(wrap____00__special_function_name))
 
+test.rename.rlike <- function() .Call(wrap__test_rename_mymod)
+
 #' Return string `"Hello world!"` to R.
 #' @export
 hello_submodule <- function() .Call(wrap__hello_submodule)
diff --git a/tests/extendrtests/src/rust/src/lib.rs b/tests/extendrtests/src/rust/src/lib.rs
index 7475b3c7a..3e8e23941 100644
--- a/tests/extendrtests/src/rust/src/lib.rs
+++ b/tests/extendrtests/src/rust/src/lib.rs
@@ -123,6 +123,17 @@ fn doubles_square(input: Doubles) -> Doubles {
     result
 }
 
+#[extendr(use_try_from = true)]
+fn complexes_square(input: Complexes) -> Complexes {
+    let mut result = Complexes::new(input.len());
+
+    for (x, y) in result.iter_mut().zip(input.iter()) {
+        *x = Rcplx::from((y.re() * y.re(), 0.0.into()));
+    }
+
+    result
+}
+
 #[extendr(use_try_from = true)]
 fn integers_square(input: Integers) -> Integers {
     let mut result = Integers::new(input.len());
@@ -170,6 +181,15 @@ fn special_param_names(_x: i32, _y: i32) -> i32 {
 #[extendr]
 #[allow(non_snake_case)]
 fn __00__special_function_name() {}
+ 
+#[extendr(
+    use_try_from = true,
+    r_name = "test.rename.rlike",
+    mod_name = "test_rename_mymod"
+)]
+fn test_rename() -> i32 {
+  1
+}
 
 // Class for testing
 #[derive(Default, Debug)]
@@ -244,6 +264,7 @@ impl MyClassUnexported {
     }
 }
 
+
 // Macro to generate exports
 extendr_module! {
     mod extendrtests;
@@ -265,6 +286,7 @@ extendr_module! {
     fn get_logicals_element;
 
     fn doubles_square;
+    fn complexes_square;
     fn integers_square;
     fn logicals_not;
 
@@ -279,6 +301,9 @@ extendr_module! {
     fn special_param_names;
     fn __00__special_function_name;
 
+    // Note that this uses an alternative name.    
+    fn test_rename_mymod;
+
     impl MyClass;
     impl __MyClass;
     impl MyClassUnexported;
diff --git a/tests/extendrtests/tests/testthat/test-vector-wrappers.R b/tests/extendrtests/tests/testthat/test-vector-wrappers.R
index a82df929c..e8433ac1e 100644
--- a/tests/extendrtests/tests/testthat/test-vector-wrappers.R
+++ b/tests/extendrtests/tests/testthat/test-vector-wrappers.R
@@ -33,6 +33,11 @@ test_that("Construct double vector from squares of given values", {
     expect_equal(doubles_square(x), x * x)
 })
 
+test_that("Construct complex vector from squares of given values", {
+    x <- as.complex(c(1.0 * (1:100)))
+    expect_equal(complexes_square(x), x * x)
+})
+
 test_that("Construct integer vector from squares of given values", {
   x <- c(1:100)
   expect_equal(integers_square(x), x * x)
diff --git a/tests/extendrtests/tests/testthat/test-wrappers.R b/tests/extendrtests/tests/testthat/test-wrappers.R
index 9c589684f..ffee703b9 100644
--- a/tests/extendrtests/tests/testthat/test-wrappers.R
+++ b/tests/extendrtests/tests/testthat/test-wrappers.R
@@ -50,6 +50,11 @@ test_that("Rust `_arg` is correctly wrapped in R", {
   expect_equal(special_param_names(`_y` = y, `_x` = x), x - y)
 })
 
+test_that("Renamed functions work correctly", {
+  expect_equal(test.rename.rlike(), 1)
+  expect_equal(.Call(wrap__test_rename_mymod), 1)
+})
+
 test_that("Call to Rust via wrapper functions works", {
   expect_equal(hello_world(), "Hello world!")
   expect_visible(hello_world())