Repository: extendr/extendr
Pull request #882 (2025-02-20 22:17:46)

Extendr proc refactor - multiple `impl` blocks and struct converions
I decided to clean up my PR branches as my code had lingering changes from #878 within the `extendr_module` that did not relate to the core of the PR.

This PR addresses #538 and is a cleanup of #878 and #881 .

This PR also includes enum support, and tests have been added to `extendrtests`.

To be explicit, this PR does the following:

```rust

use extendr_api::prelude::*;

//now required if impl block function 
// takes Self or returns Self
#[extendr] 
struct Counter {
    n: i32,
}

mod create {
    use super::*;

    #[extendr]
    impl Counter {
        fn new() -> Self {
            Self { n: 0 }
        }
    }

    extendr_module! {
      mod create;
      impl Counter;
    }
}

mod increment {
    use super::*;

    #[extendr]
    impl Counter {
        fn get(&self) -> i32 {
            self.n
        }

        fn increment(&mut self) {
            self.n += 1
        }
    }

    extendr_module! {
      mod increment;
      impl Counter;
    }
}

extendr_module! {
  mod counter;
  use create;
  use increment;
}
```

JosiahParry: Note that this PR will require that we update the user guide here: https://github.com/extendr/extendr.github.io/blob/main/user-guide/type-mapping/extendr-macro.qmd
JosiahParry: Testing this with [tomledit](https://github.com/JosiahParry/tomledit) and it immediately throws an error 
<img width="628" alt="image" src="https://github.com/user-attachments/assets/c3f98022-3228-4419-81b1-9c270da22015" />
I'm not familiar with proc-macros, is there any way we can make the compiler suggest adding `#[extendr]` to the struct definition? 

Adding it to the struct definition allows the package to be compiled and runs as intended.

One thing I'll also note is that this PR also allows annotating a struct with `#[extendr]` _without_ an `impl` block allows you to return the type—which is something I've been wanting! 

For example the following works _without_ using an ExternalPtr on the struct type or having an empty impl block. I'm all for that. 

```rs
#[extendr]
#[derive(Clone)]
pub struct Toml(pub DocumentMut);

#[extendr]
fn test_out() -> Toml {
    Toml::new()
}


extendr_module! {
    mod tomledit;
    fn test_out;
}
```

Moving impls out and into their own (private) modules also works. 

This is looking really good to me

jtlandis: After spending some time researching (and begging chat-gpt for guidance) it seems that there may be a solution with [`#[rustc_on_unimplemented]`](https://doc.rust-lang.org/error_codes/E0230.html).

I dont really know what that would look like to implement in extendr though.


JosiahParry: Super nice find! I don't think its required to fix that here. `ToVectorValue` is a bit of a pain-point that can be handled later. 

It is also worth noting in the docs that if using `#[extendr]` on a struct / enum then custom `TryFrom<>` or `From<>` implementations **cannot** be used.

@CGMossa do you have any thoughts on this PR?
CGMossa: I would say merge it. We should just make an issue, that dictate that I review these changes, and amend them myself, before we cut a crates.io release.

That seems very reasonable. And I've had a cursory read of the code.... It is better than several of my prototypes.. But proc macro code is impossible to review, without getting your hands dirty...

Approved! 
jtlandis: Before we do, should I add stuff to the docs and the change log?
JosiahParry: Sounds like a plan. 

@jtlandis could you update the changelog? You can use [skip ci] in the message. 

- make an issue in extendr.github.io 
- Then can you also make a review issue for mossa in this repo?
- 
that should cover it
jtlandis: @JosiahParry I have a few questions:

1. Should I make a new heading for "Breaking Changes" within the Changelog, or just put this under the Added heading?
2. I am unsure where the docs/repo is for extendr.github.io, are the docs built from [rextendr](https://github.com/extendr/rextendr)?
JosiahParry: 1. You got it! 
2. Here: https://github.com/extendr/extendr.github.io/issues/ ! 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 475133585..cd75985c3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 ### Added
 
+- Type aliases `RMatrix4D` and `RMatrix5D` have been added <https://github.com/extendr/extendr/pull/875>
 - `ExternalPtr<T>` and `&ExternalPtr<T>` can now be used as function arguments with appropriate type checking <https://github.com/extendr/extendr/pull/853>
 - `TryFrom<Robj>` is now implemented for `&ExternalPtr<T>` and `&mut ExternalPtr<T>` <https://github.com/extendr/extendr/pull/833>
 - `Option<T>` can now be returned from `#[extendr]` annotated functions for extendr_api types. `None` is translated into `NULL` value in R [[#802]](https://github.com/extendr/extendr/pull/802)
@@ -15,6 +16,7 @@
 
 ### Changed
 
+- Breaking change: Adding `#[extendr]` above impl blocks no longer provides a default implementation for `impl From<T> for Robj` and `impl TryFrom<Robj> for T`. Instead, users may annotate their custom structs and enums with `#[extendr]` to get these default implementations. Note that a user can still derive their own implementations for these traits, and in this case should forego annotating their type with `#[extendr]`. With this change, users may use `#[extendr]` on multiple `impl <T>` blocks so long as they are contained within their own modules. <https://github.com/extendr/extendr/pull/882>
 - Enhancement: string comparisons are now implemented for `Rstr` using R's string intern mechanism. Making string comparisons _much_ faster. [[#845]](https://github.com/extendr/extendr/pull/845)
 - Breaking change: `RMatrix::get_rownames` and `RMatrix::get_colnames` now both
 return `Option<Strings>` instead of opaque `Robj`.
diff --git a/README.md b/README.md
index 106dd2826..aab613cfa 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,7 @@ declaration and the methods in the `impl`.
 ```rust
 use extendr_api::prelude::*;
 
+#[extendr]
 struct Person {
     pub name: String,
 }
diff --git a/extendr-api/src/error.rs b/extendr-api/src/error.rs
index c3fef3c07..603732988 100644
--- a/extendr-api/src/error.rs
+++ b/extendr-api/src/error.rs
@@ -65,6 +65,8 @@ pub enum Error {
     ExpectedVector(Robj),
     ExpectedMatrix(Robj),
     ExpectedMatrix3D(Robj),
+    ExpectedMatrix4D(Robj),
+    ExpectedMatrix5D(Robj),
     ExpectedNumeric(Robj),
     ExpectedAltrep(Robj),
     ExpectedDataframe(Robj),
@@ -144,6 +146,8 @@ impl std::fmt::Display for Error {
             Error::ExpectedVector(robj) => write!(f, "Expected Vector, got {:?}", robj.rtype()),
             Error::ExpectedMatrix(robj) => write!(f, "Expected Matrix, got {:?}", robj.rtype()),
             Error::ExpectedMatrix3D(robj) => write!(f, "Expected Matrix3D, got {:?}", robj.rtype()),
+            Error::ExpectedMatrix4D(robj) => write!(f, "Expected Matrix4D, got {:?}", robj.rtype()),
+            Error::ExpectedMatrix5D(robj) => write!(f, "Expected Matrix5D, got {:?}", robj.rtype()),
             Error::ExpectedNumeric(robj) => write!(f, "Expected Numeric, got {:?}", robj.rtype()),
             Error::ExpectedAltrep(robj) => write!(f, "Expected Altrep, got {:?}", robj.rtype()),
             Error::ExpectedDataframe(robj) => {
diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
index 2bfa2d3ff..5aba4b0c5 100644
--- a/extendr-api/src/lib.rs
+++ b/extendr-api/src/lib.rs
@@ -21,7 +21,7 @@
 //! // define exports using extendr_module
 //! extendr_module! {
 //!    mod mymodule;
-//!    fn fred;    
+//!    fn fred;
 //! }
 //!
 //! ```
@@ -41,26 +41,26 @@
 //!     // An R object with a single string "hello"
 //!     let character = r!("hello");
 //!     let character = r!(["hello", "goodbye"]);
-//!    
+//!
 //!     // An R integer object with a single number 1L.
 //!     // Note that in Rust, 1 is an integer and 1.0 is a real.
 //!     let integer = r!(1);
-//!    
+//!
 //!     // An R real object with a single number 1.
 //!     // Note that in R, 1 is a real and 1L is an integer.
 //!     let real = r!(1.0);
-//!    
+//!
 //!     // An R real vector.
 //!     let real_vector = r!([1.0, 2.0]);
 //!     let real_vector = &[1.0, 2.0].iter().collect_robj();
 //!     let real_vector = r!(vec![1.0, 2.0]);
-//!    
+//!
 //!     // An R function object.
 //!     let function = R!("function(x, y) { x + y }")?;
-//!    
+//!
 //!     // A named list using the list! macro.
 //!     let list = list!(a = 1, b = 2);
-//!    
+//!
 //!     // An unnamed list (of R objects) using the List wrapper.
 //!     let list = r!(List::from_values(vec![1, 2, 3]));
 //!     let list = r!(List::from_values(vec!["a", "b", "c"]));
@@ -82,11 +82,11 @@
 //!     // 1 ..= 100 is the same as 1:100
 //!     let res = r!(1 ..= 100);
 //!     assert_eq!(res, R!("1:100")?);
-//!    
+//!
 //!     // Rust arrays are zero-indexed so it is more common to use 0 .. 100.
 //!     let res = r!(0 .. 100);
 //!     assert_eq!(res.len(), 100);
-//!    
+//!
 //!     // Using map is a super fast way to generate vectors.
 //!     let iter = (0..3).map(|i| format!("fred{}", i));
 //!     let character = iter.collect_robj();
@@ -115,13 +115,13 @@
 //! use extendr_api::prelude::*;
 //! test! {
 //!     let vals = r!([1.0, 2.0, 3.0]);
-//!    
+//!
 //!     // one-based indexing [[i]], returns an object.
 //!     assert_eq!(vals.index(1)?, r!(1.0));
-//!    
+//!
 //!     // one-based slicing [x], returns an object.
 //!     assert_eq!(vals.slice(1..=2)?, r!([1.0, 2.0]));
-//!    
+//!
 //!     // $ operator, returns an object
 //!     let list = list!(a = 1.0, b = "xyz");
 //!     assert_eq!(list.dollar("a")?, r!(1.0));
@@ -191,7 +191,7 @@
 //! test! {
 //!     // robj is an "Owned" object that controls the memory allocated.
 //!     let robj = r!([1, 2, 3]);
-//!    
+//!
 //!     // Here slice is a "borrowed" reference to the bytes in robj.
 //!     // and cannot live longer than robj.
 //!     let slice = robj.as_integer_slice().ok_or("expected slice")?;
@@ -215,7 +215,7 @@
 //! // define exports using extendr_module
 //! extendr_module! {
 //!    mod mymodule;
-//!    fn things;    
+//!    fn things;
 //! }
 //!
 //!
@@ -253,7 +253,7 @@
 //! // define exports using extendr_module
 //! extendr_module! {
 //!    mod mymodule;
-//!    fn oups;    
+//!    fn oups;
 //! }
 //!
 //! ```
@@ -279,7 +279,7 @@
 //!
 //! # handling example for result_condition
 //! oups_handled <- function(a) {
-//!   val_or_err <- oups(1)  
+//!   val_or_err <- oups(1)
 //!   if (inherits(val_or_err, "extendr_error")) stop(val_or_err)
 //!   val_or_err
 //! }
@@ -760,6 +760,7 @@ mod tests {
         x
     }
 
+    #[extendr]
     struct Person {
         pub name: String,
     }
diff --git a/extendr-api/src/metadata.rs b/extendr-api/src/metadata.rs
index 4cbd41343..887860b45 100644
--- a/extendr-api/src/metadata.rs
+++ b/extendr-api/src/metadata.rs
@@ -297,27 +297,36 @@ fn write_method_wrapper(
 /// Generate a wrapper for an implementation block.
 fn write_impl_wrapper(
     w: &mut Vec<u8>,
-    imp: &Impl,
+    name: &str,
+    impls: &[Impl],
     package_name: &str,
     use_symbols: bool,
 ) -> std::io::Result<()> {
-    let exported = imp.doc.contains("@export");
-
-    write_doc(w, imp.doc)?;
+    let mut exported = false;
+    {
+        for imp in impls.iter().filter(|imp| imp.name == name) {
+            if !exported {
+                exported = imp.doc.contains("@export");
+            }
+            write_doc(w, imp.doc)?;
+        }
+    }
 
-    let imp_name_fixed = sanitize_identifier(imp.name);
+    let imp_name_fixed = sanitize_identifier(name);
 
     // Using fixed name because it is exposed to R
     writeln!(w, "{} <- new.env(parent = emptyenv())\n", imp_name_fixed)?;
 
-    for func in &imp.methods {
-        // write_doc(& mut w, func.doc)?;
-        // `imp.name` is passed as is and sanitized within the function
-        write_method_wrapper(w, func, package_name, use_symbols, imp.name)?;
+    for imp in impls.iter().filter(|imp| imp.name == name) {
+        for func in &imp.methods {
+            // write_doc(& mut w, func.doc)?;
+            // `imp.name` is passed as is and sanitized within the function
+            write_method_wrapper(w, func, package_name, use_symbols, imp.name)?;
+        }
     }
 
     if exported {
-        writeln!(w, "#' @rdname {}", imp.name)?;
+        writeln!(w, "#' @rdname {}", name)?;
         writeln!(w, "#' @usage NULL")?;
     }
 
@@ -330,10 +339,10 @@ fn write_impl_wrapper(
     // LHS with dollar operator is wrapped in ``, so pass name as is,
     // but in the body `imp_name_fixed` is called as valid R function,
     // so we pass preprocessed value
-    writeln!(w, "`$.{}` <- function (self, name) {{ func <- {}[[name]]; environment(func) <- environment(); func }}\n", imp.name, imp_name_fixed)?;
+    writeln!(w, "`$.{}` <- function (self, name) {{ func <- {}[[name]]; environment(func) <- environment(); func }}\n", name, imp_name_fixed)?;
 
     writeln!(w, "#' @export")?;
-    writeln!(w, "`[[.{}` <- `$.{}`\n", imp.name, imp.name)?;
+    writeln!(w, "`[[.{}` <- `$.{}`\n", name, name)?;
 
     Ok(())
 }
@@ -369,9 +378,20 @@ impl Metadata {
             write_function_wrapper(&mut w, func, package_name, use_symbols)?;
         }
 
-        for imp in &self.impls {
-            write_impl_wrapper(&mut w, imp, package_name, use_symbols)?;
+        for name in self.impl_names() {
+            write_impl_wrapper(&mut w, name, &self.impls, package_name, use_symbols)?;
         }
+
         unsafe { Ok(String::from_utf8_unchecked(w)) }
     }
+
+    fn impl_names<'a>(&'a self) -> Vec<&'a str> {
+        let mut vec: Vec<&str> = vec![];
+        for impls in &self.impls {
+            if !vec.contains(&impls.name) {
+                vec.push(&impls.name)
+            }
+        }
+        vec
+    }
 }
diff --git a/extendr-api/src/prelude.rs b/extendr-api/src/prelude.rs
index a56ff63b6..80cf72fd1 100644
--- a/extendr-api/src/prelude.rs
+++ b/extendr-api/src/prelude.rs
@@ -38,7 +38,7 @@ pub use crate::{
 
 pub use super::wrapper::{
     AltComplexImpl, AltIntegerImpl, AltLogicalImpl, AltRawImpl, AltRealImpl, AltStringImpl, Altrep,
-    AltrepImpl, RArray, RColumn, RMatrix, RMatrix3D,
+    AltrepImpl, RArray, RColumn, RMatrix, RMatrix3D, RMatrix4D, RMatrix5D,
 };
 
 #[cfg(use_r_altlist)]
diff --git a/extendr-api/src/wrapper/matrix.rs b/extendr-api/src/wrapper/matrix.rs
index 0f83e523b..cb5333fd4 100644
--- a/extendr-api/src/wrapper/matrix.rs
+++ b/extendr-api/src/wrapper/matrix.rs
@@ -72,6 +72,8 @@ impl<T, D> RArray<T, D> {
 pub type RColumn<T> = RArray<T, [usize; 1]>;
 pub type RMatrix<T> = RArray<T, [usize; 2]>;
 pub type RMatrix3D<T> = RArray<T, [usize; 3]>;
+pub type RMatrix4D<T> = RArray<T, [usize; 4]>;
+pub type RMatrix5D<T> = RArray<T, [usize; 5]>;
 
 impl<T> RMatrix<T>
 where
@@ -385,6 +387,60 @@ where
     }
 }
 
+impl<T> TryFrom<&Robj> for RMatrix4D<T>
+where
+    Robj: for<'a> AsTypedSlice<'a, T>,
+{
+    type Error = Error;
+
+    fn try_from(robj: &Robj) -> Result<Self> {
+        if let Some(_slice) = robj.as_typed_slice() {
+            if let Some(dim) = robj.dim() {
+                if dim.len() != 4 {
+                    Err(Error::ExpectedMatrix4D(robj.clone()))
+                } else {
+                    let dim: Vec<_> = dim.iter().map(|d| d.inner() as usize).collect();
+                    Ok(RArray::from_parts(
+                        robj.clone(),
+                        [dim[0], dim[1], dim[2], dim[3]],
+                    ))
+                }
+            } else {
+                Err(Error::ExpectedMatrix4D(robj.clone()))
+            }
+        } else {
+            Err(Error::TypeMismatch(robj.clone()))
+        }
+    }
+}
+
+impl<T> TryFrom<&Robj> for RMatrix5D<T>
+where
+    Robj: for<'a> AsTypedSlice<'a, T>,
+{
+    type Error = Error;
+
+    fn try_from(robj: &Robj) -> Result<Self> {
+        if let Some(_slice) = robj.as_typed_slice() {
+            if let Some(dim) = robj.dim() {
+                if dim.len() != 5 {
+                    Err(Error::ExpectedMatrix5D(robj.clone()))
+                } else {
+                    let dim: Vec<_> = dim.iter().map(|d| d.inner() as usize).collect();
+                    Ok(RArray::from_parts(
+                        robj.clone(),
+                        [dim[0], dim[1], dim[2], dim[3], dim[4]],
+                    ))
+                }
+            } else {
+                Err(Error::ExpectedMatrix5D(robj.clone()))
+            }
+        } else {
+            Err(Error::TypeMismatch(robj.clone()))
+        }
+    }
+}
+
 macro_rules! impl_try_from_robj_ref {
     ($($type : tt)*) => {
         $(
@@ -432,6 +488,8 @@ impl_try_from_robj_ref!(
     RMatrix
     RColumn
     RMatrix3D
+    RMatrix4D
+    RMatrix5D
 );
 
 impl<T, D> From<RArray<T, D>> for Robj {
diff --git a/extendr-api/src/wrapper/mod.rs b/extendr-api/src/wrapper/mod.rs
index 7dc478dba..c7d66415a 100644
--- a/extendr-api/src/wrapper/mod.rs
+++ b/extendr-api/src/wrapper/mod.rs
@@ -48,7 +48,7 @@ pub use integers::Integers;
 pub use lang::Language;
 pub use list::{FromList, List, ListIter};
 pub use logicals::Logicals;
-pub use matrix::{MatrixConversions, RArray, RColumn, RMatrix, RMatrix3D};
+pub use matrix::{MatrixConversions, RArray, RColumn, RMatrix, RMatrix3D, RMatrix4D, RMatrix5D};
 pub use nullable::Nullable;
 pub use pairlist::{Pairlist, PairlistIter};
 pub use primitive::Primitive;
diff --git a/extendr-macros/src/extendr_conversion.rs b/extendr-macros/src/extendr_conversion.rs
new file mode 100644
index 000000000..c4682b039
--- /dev/null
+++ b/extendr-macros/src/extendr_conversion.rs
@@ -0,0 +1,132 @@
+use crate::{extendr_options::ExtendrOptions, wrappers};
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::Attribute;
+use syn::Generics;
+use syn::Ident;
+use syn::Item;
+
+struct TypeFields {
+    ident: Ident,
+    generics: Generics,
+    attrs: Vec<Attribute>,
+}
+
+fn pull_fields(item: &Item) -> syn::Result<TypeFields> {
+    let fields = match item {
+        Item::Struct(str) => TypeFields {
+            ident: str.ident.clone(),
+            generics: str.generics.clone(),
+            attrs: str.attrs.clone(),
+        },
+        Item::Enum(str) => TypeFields {
+            ident: str.ident.clone(),
+            generics: str.generics.clone(),
+            attrs: str.attrs.clone(),
+        },
+        _ => {
+            return Err(syn::Error::new_spanned(
+                item,
+                "#[extendr] conversions can only be built for `struct` and `enum`",
+            ))
+        }
+    };
+    Ok(fields)
+}
+
+pub(crate) fn extendr_type_conversion(item: Item, opts: &ExtendrOptions) -> TokenStream {
+    match do_extendr_type_conversion(item, opts) {
+        Ok(result) => result,
+        Err(e) => e.to_compile_error().into(),
+    }
+}
+
+fn do_extendr_type_conversion(item: Item, _opts: &ExtendrOptions) -> syn::Result<TokenStream> {
+    let TypeFields {
+        ident: self_ty,
+        generics,
+        attrs,
+    } = pull_fields(&item)?;
+    if generics.const_params().count() != 0 {
+        return Err(syn::Error::new_spanned(
+            item,
+            "const params not allowed in #[extendr] impl",
+        ));
+    }
+    let mut self_ty_name = self_ty.to_string();
+    for gen in generics.type_params() {
+        self_ty_name.push('_');
+        self_ty_name.push_str(gen.ident.to_string().as_str());
+    }
+    // residual code syntax from extendr_impl.rs
+    // let mut _prefix = format!("{}__", self_ty_name);
+
+    // To do: Should documenting the struct be moved to R?
+    // At the moment, only documentattion above the impl
+    // block makes it to R.
+    let _doc_string = wrappers::get_doc_string(&attrs);
+
+    let conversion_impls = quote! {
+        // Output conversion function for this type.
+
+        impl TryFrom<Robj> for &#self_ty {
+            type Error = extendr_api::Error;
+
+            fn try_from(robj: Robj) -> extendr_api::Result<Self> {
+                Self::try_from(&robj)
+            }
+        }
+
+        impl TryFrom<Robj> for &mut #self_ty {
+            type Error = extendr_api::Error;
+
+            fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
+                Self::try_from(&mut robj)
+            }
+        }
+
+        // Output conversion function for this type.
+        impl TryFrom<&Robj> for &#self_ty {
+            type Error = extendr_api::Error;
+            fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
+                use extendr_api::ExternalPtr;
+                unsafe {
+                    let external_ptr: &ExternalPtr<#self_ty> = robj.try_into()?;
+                    external_ptr.try_addr()
+                }
+            }
+        }
+
+        // Input conversion function for a mutable reference to this type.
+        impl TryFrom<&mut Robj> for &mut #self_ty {
+            type Error = extendr_api::Error;
+            fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
+                use extendr_api::ExternalPtr;
+                unsafe {
+                    let external_ptr: &mut ExternalPtr<#self_ty> = robj.try_into()?;
+                    external_ptr.try_addr_mut()
+                }
+            }
+        }
+    };
+
+    let output = TokenStream::from(quote! {
+        #item
+
+        #conversion_impls
+
+        // Output conversion function for this type.
+        impl From<#self_ty> for Robj {
+            fn from(value: #self_ty) -> Self {
+                use extendr_api::ExternalPtr;
+                unsafe {
+                    let mut res: ExternalPtr<#self_ty> = ExternalPtr::new(value);
+                    res.set_attrib(class_symbol(), #self_ty_name).unwrap();
+                    res.into()
+                }
+            }
+        }
+
+    });
+    Ok(output)
+}
diff --git a/extendr-macros/src/extendr_impl.rs b/extendr-macros/src/extendr_impl.rs
index 8b84035f1..472c74f3a 100644
--- a/extendr-macros/src/extendr_impl.rs
+++ b/extendr-macros/src/extendr_impl.rs
@@ -58,7 +58,7 @@ use crate::wrappers;
 ///         // return self
 ///         self
 ///     }
-///     
+///
 ///     // Convert the struct into a data.frame
 ///     fn into_df(&self) -> Robj {
 ///         let df = self.0.clone().into_dataframe();
@@ -174,50 +174,6 @@ pub(crate) fn extendr_impl(
 
     let meta_name = format_ident!("{}{}", wrappers::META_PREFIX, self_ty_name);
 
-    let conversion_impls = quote! {
-        // Output conversion function for this type.
-
-        impl TryFrom<Robj> for &#self_ty {
-            type Error = extendr_api::Error;
-
-            fn try_from(robj: Robj) -> extendr_api::Result<Self> {
-                Self::try_from(&robj)
-            }
-        }
-
-        impl TryFrom<Robj> for &mut #self_ty {
-            type Error = extendr_api::Error;
-
-            fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
-                Self::try_from(&mut robj)
-            }
-        }
-
-        // Output conversion function for this type.
-        impl TryFrom<&Robj> for &#self_ty {
-            type Error = extendr_api::Error;
-            fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
-                use extendr_api::ExternalPtr;
-                unsafe {
-                    let external_ptr: &ExternalPtr<#self_ty> = robj.try_into()?;
-                    external_ptr.try_addr()
-                }
-            }
-        }
-
-        // Input conversion function for a mutable reference to this type.
-        impl TryFrom<&mut Robj> for &mut #self_ty {
-            type Error = extendr_api::Error;
-            fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
-                use extendr_api::ExternalPtr;
-                unsafe {
-                    let external_ptr: &mut ExternalPtr<#self_ty> = robj.try_into()?;
-                    external_ptr.try_addr_mut()
-                }
-            }
-        }
-    };
-
     let expanded = TokenStream::from(quote! {
         // The impl itself copied from the source.
         #item_impl
@@ -225,20 +181,6 @@ pub(crate) fn extendr_impl(
         // Function wrappers
         #( #wrappers )*
 
-        #conversion_impls
-
-        // Output conversion function for this type.
-        impl From<#self_ty> for Robj {
-            fn from(value: #self_ty) -> Self {
-                use extendr_api::ExternalPtr;
-                unsafe {
-                    let mut res: ExternalPtr<#self_ty> = ExternalPtr::new(value);
-                    res.set_attrib(class_symbol(), #self_ty_name).unwrap();
-                    res.into()
-                }
-            }
-        }
-
         #[allow(non_snake_case)]
         fn #meta_name(impls: &mut Vec<extendr_api::metadata::Impl>) {
             let mut methods = Vec::new();
diff --git a/extendr-macros/src/lib.rs b/extendr-macros/src/lib.rs
index 8815d54ee..bec14776e 100644
--- a/extendr-macros/src/lib.rs
+++ b/extendr-macros/src/lib.rs
@@ -58,6 +58,7 @@
 mod R;
 mod call;
 mod dataframe;
+mod extendr_conversion;
 mod extendr_function;
 mod extendr_impl;
 mod extendr_module;
@@ -91,6 +92,8 @@ pub fn extendr(attr: TokenStream, item: TokenStream) -> TokenStream {
     parse_macro_input!(attr with extendr_opts_parser);
 
     match parse_macro_input!(item as Item) {
+        Item::Struct(str) => extendr_conversion::extendr_type_conversion(Item::Struct(str), &opts),
+        Item::Enum(enm) => extendr_conversion::extendr_type_conversion(Item::Enum(enm), &opts),
         Item::Fn(func) => extendr_function::extendr_function(func, &opts),
         Item::Impl(item_impl) => match extendr_impl::extendr_impl(item_impl, &opts) {
             Ok(result) => result,


Test patch:
diff --git a/extendr-api/tests/altrep_tests.rs b/extendr-api/tests/altrep_tests.rs
index 1d269530d..e799f3a69 100644
--- a/extendr-api/tests/altrep_tests.rs
+++ b/extendr-api/tests/altrep_tests.rs
@@ -244,6 +244,7 @@ fn test_altlist() {
     use extendr_api::AltListImpl;
     with_r(|| {
         #[derive(Debug, Clone)]
+        #[extendr]
         pub struct VecUsize(pub Vec<Option<usize>>);
 
         // need to make the VecUsize object `.into_robj()`-able
diff --git a/tests/extendrtests/NAMESPACE b/tests/extendrtests/NAMESPACE
index cbb2628e9..ea2deee4c 100644
--- a/tests/extendrtests/NAMESPACE
+++ b/tests/extendrtests/NAMESPACE
@@ -1,11 +1,13 @@
 # Generated by roxygen2: do not edit by hand
 
 S3method("$","__MyClass")
+S3method("$",Animal)
 S3method("$",MyClass)
 S3method("$",MyClassUnexported)
 S3method("$",MySubmoduleClass)
 S3method("$",Wrapper)
 S3method("[[","__MyClass")
+S3method("[[",Animal)
 S3method("[[",MyClass)
 S3method("[[",MyClassUnexported)
 S3method("[[",MySubmoduleClass)
diff --git a/tests/extendrtests/R/extendr-wrappers.R b/tests/extendrtests/R/extendr-wrappers.R
index f668e13ec..b2c0dc8e1 100644
--- a/tests/extendrtests/R/extendr-wrappers.R
+++ b/tests/extendrtests/R/extendr-wrappers.R
@@ -181,6 +181,12 @@ fetch_rownames <- function(x) .Call(wrap__fetch_rownames, x)
 
 change_dimnames <- function(x) .Call(wrap__change_dimnames, x)
 
+matrix_3d_return <- function(x) .Call(wrap__matrix_3d_return, x)
+
+matrix_4d_return <- function(x) .Call(wrap__matrix_4d_return, x)
+
+matrix_5d_return <- function(x) .Call(wrap__matrix_5d_return, x)
+
 to_unique_rstr <- function(r_char_vec) .Call(wrap__to_unique_rstr, r_char_vec)
 
 to_unique_str <- function(r_char_vec) .Call(wrap__to_unique_str, r_char_vec)
@@ -293,6 +299,8 @@ Wrapper$max_ref_offset <- function(other, `_offset`) .Call(wrap__Wrapper__max_re
 
 Wrapper$max_ref2 <- function(other) .Call(wrap__Wrapper__max_ref2, self, other)
 
+Wrapper$a_10 <- function() .Call(wrap__Wrapper__a_10, self)
+
 #' @rdname Wrapper
 #' @usage NULL
 #' @export
@@ -301,5 +309,19 @@ Wrapper$max_ref2 <- function(other) .Call(wrap__Wrapper__max_ref2, self, other)
 #' @export
 `[[.Wrapper` <- `$.Wrapper`
 
+Animal <- new.env(parent = emptyenv())
+
+Animal$new_dog <- function() .Call(wrap__Animal__new_dog)
+
+Animal$new_cat <- function() .Call(wrap__Animal__new_cat)
+
+Animal$speak <- function() .Call(wrap__Animal__speak, self)
+
+#' @export
+`$.Animal` <- function (self, name) { func <- Animal[[name]]; environment(func) <- environment(); func }
+
+#' @export
+`[[.Animal` <- `$.Animal`
+
 
 # nolint end
diff --git a/tests/extendrtests/man/Wrapper.Rd b/tests/extendrtests/man/Wrapper.Rd
index d207c744a..21d98cc71 100644
--- a/tests/extendrtests/man/Wrapper.Rd
+++ b/tests/extendrtests/man/Wrapper.Rd
@@ -6,7 +6,7 @@
 \alias{$.Wrapper}
 \title{Class for testing (exported)}
 \format{
-An object of class \code{environment} of length 11.
+An object of class \code{environment} of length 12.
 }
 \usage{
 Wrapper
diff --git a/tests/extendrtests/src/rust/src/altrep.rs b/tests/extendrtests/src/rust/src/altrep.rs
index f71b4e165..4e75fe606 100644
--- a/tests/extendrtests/src/rust/src/altrep.rs
+++ b/tests/extendrtests/src/rust/src/altrep.rs
@@ -2,6 +2,7 @@ use extendr_api::prelude::*;
 
 // struct contains an inner vector of Option<usize>
 #[derive(Debug, Clone)]
+#[extendr]
 pub struct VecUsize(pub Vec<Option<usize>>);
 
 impl AltrepImpl for VecUsize {
diff --git a/tests/extendrtests/src/rust/src/externalptr.rs b/tests/extendrtests/src/rust/src/externalptr.rs
index 00f4069e4..26dca38f3 100644
--- a/tests/extendrtests/src/rust/src/externalptr.rs
+++ b/tests/extendrtests/src/rust/src/externalptr.rs
@@ -2,6 +2,7 @@ use extendr_api::prelude::*;
 
 // Class for testing
 #[derive(Default, Debug, Clone, Copy)]
+#[extendr]
 struct Wrapper {
     a: i32,
 }
@@ -107,11 +108,49 @@ fn sum_integer_externalptr(x: ExternalPtr<Integers>) -> Rint {
     x.into_iter().sum()
 }
 
+mod submod {
+    use super::*;
+
+    #[extendr]
+    impl Wrapper {
+        pub fn a_10(&self) -> i32 {
+            self.a + 10
+        }
+    }
+    extendr_module! {
+      mod submod;
+      impl Wrapper;
+    }
+}
+
+#[extendr]
+enum Animal {
+    Cat,
+    Dog,
+}
+
+#[extendr]
+impl Animal {
+    pub fn new_dog() -> Self {
+        Animal::Dog
+    }
+    pub fn new_cat() -> Self {
+        Animal::Cat
+    }
+    pub fn speak(&self) -> Strings {
+        match self {
+            Animal::Dog => Strings::from("woof"),
+            Animal::Cat => Strings::from("meow"),
+        }
+    }
+}
+
 // Macro to generate exports
 extendr_module! {
     mod externalptr;
     impl Wrapper;
-
+    use submod;
     fn create_numeric_externalptr;
     fn sum_integer_externalptr;
+    impl Animal;
 }
diff --git a/tests/extendrtests/src/rust/src/lib.rs b/tests/extendrtests/src/rust/src/lib.rs
index 882089ed7..2ed7054ef 100644
--- a/tests/extendrtests/src/rust/src/lib.rs
+++ b/tests/extendrtests/src/rust/src/lib.rs
@@ -203,6 +203,7 @@ fn add_5_if_not_null(x: Nullable<Rint>) -> Nullable<Rint> {
 
 // Class for testing
 #[derive(Default, Debug)]
+#[extendr]
 struct MyClass {
     a: i32,
 }
@@ -251,6 +252,7 @@ impl MyClass {
 
 // Class for testing special names
 #[derive(Default, Debug)]
+#[extendr]
 struct __MyClass {}
 
 // Class for testing special names
@@ -267,6 +269,7 @@ impl __MyClass {
 
 // Class for testing (unexported)
 #[derive(Default, Debug)]
+#[extendr]
 struct MyClassUnexported {
     a: i32,
 }
diff --git a/tests/extendrtests/src/rust/src/matrix.rs b/tests/extendrtests/src/rust/src/matrix.rs
index 425fdadfa..16ce5319a 100644
--- a/tests/extendrtests/src/rust/src/matrix.rs
+++ b/tests/extendrtests/src/rust/src/matrix.rs
@@ -22,10 +22,28 @@ fn change_dimnames(mut x: RMatrix<f64>) -> Robj {
     x.to_owned()
 }
 
+#[extendr]
+fn matrix_3d_return(x: RMatrix3D<f64>) -> RMatrix3D<f64> {
+    x
+}
+
+#[extendr]
+fn matrix_4d_return(x: RMatrix4D<f64>) -> RMatrix4D<f64> {
+    x
+}
+
+#[extendr]
+fn matrix_5d_return(x: RMatrix5D<f64>) -> RMatrix5D<f64> {
+    x
+}
+
 extendr_module! {
     mod matrix;
     fn fetch_dimnames;
     fn fetch_colnames;
     fn fetch_rownames;
     fn change_dimnames;
+    fn matrix_3d_return;
+    fn matrix_4d_return;
+    fn matrix_5d_return;
 }
diff --git a/tests/extendrtests/src/rust/src/submodule.rs b/tests/extendrtests/src/rust/src/submodule.rs
index 22e468e99..8e5fad10f 100644
--- a/tests/extendrtests/src/rust/src/submodule.rs
+++ b/tests/extendrtests/src/rust/src/submodule.rs
@@ -9,6 +9,7 @@ fn hello_submodule() -> &'static str {
 
 // Class for testing
 #[derive(Default, Debug, Clone, Copy)]
+#[extendr]
 struct MySubmoduleClass {
     a: i32,
 }
diff --git a/tests/extendrtests/tests/testthat/_snaps/macro-snapshot.md b/tests/extendrtests/tests/testthat/_snaps/macro-snapshot.md
index b062e6147..97d867fd4 100644
--- a/tests/extendrtests/tests/testthat/_snaps/macro-snapshot.md
+++ b/tests/extendrtests/tests/testthat/_snaps/macro-snapshot.md
@@ -12,27 +12,6 @@
       mod altrep {
           use extendr_api::prelude::*;
           pub struct VecUsize(pub Vec<Option<usize>>);
-          #[automatically_derived]
-          impl ::core::fmt::Debug for VecUsize {
-              #[inline]
-              fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
-                  ::core::fmt::Formatter::debug_tuple_field1_finish(f, "VecUsize", &&self.0)
-              }
-          }
-          #[automatically_derived]
-          impl ::core::clone::Clone for VecUsize {
-              #[inline]
-              fn clone(&self) -> VecUsize {
-                  VecUsize(::core::clone::Clone::clone(&self.0))
-              }
-          }
-          impl AltrepImpl for VecUsize {
-              fn length(&self) -> usize {
-                  self.0.len()
-              }
-          }
-          #[cfg(use_r_altlist)]
-          impl VecUsize {}
           impl TryFrom<Robj> for &VecUsize {
               type Error = extendr_api::Error;
               fn try_from(robj: Robj) -> extendr_api::Result<Self> {
@@ -75,6 +54,27 @@
                   }
               }
           }
+          #[automatically_derived]
+          impl ::core::fmt::Debug for VecUsize {
+              #[inline]
+              fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+                  ::core::fmt::Formatter::debug_tuple_field1_finish(f, "VecUsize", &&self.0)
+              }
+          }
+          #[automatically_derived]
+          impl ::core::clone::Clone for VecUsize {
+              #[inline]
+              fn clone(&self) -> VecUsize {
+                  VecUsize(::core::clone::Clone::clone(&self.0))
+              }
+          }
+          impl AltrepImpl for VecUsize {
+              fn length(&self) -> usize {
+                  self.0.len()
+              }
+          }
+          #[cfg(use_r_altlist)]
+          impl VecUsize {}
           #[allow(non_snake_case)]
           fn meta__VecUsize(impls: &mut Vec<extendr_api::metadata::Impl>) {
               let mut methods = Vec::new();
@@ -1211,6 +1211,48 @@
           struct Wrapper {
               a: i32,
           }
+          impl TryFrom<Robj> for &Wrapper {
+              type Error = extendr_api::Error;
+              fn try_from(robj: Robj) -> extendr_api::Result<Self> {
+                  Self::try_from(&robj)
+              }
+          }
+          impl TryFrom<Robj> for &mut Wrapper {
+              type Error = extendr_api::Error;
+              fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
+                  Self::try_from(&mut robj)
+              }
+          }
+          impl TryFrom<&Robj> for &Wrapper {
+              type Error = extendr_api::Error;
+              fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let external_ptr: &ExternalPtr<Wrapper> = robj.try_into()?;
+                      external_ptr.try_addr()
+                  }
+              }
+          }
+          impl TryFrom<&mut Robj> for &mut Wrapper {
+              type Error = extendr_api::Error;
+              fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let external_ptr: &mut ExternalPtr<Wrapper> = robj.try_into()?;
+                      external_ptr.try_addr_mut()
+                  }
+              }
+          }
+          impl From<Wrapper> for Robj {
+              fn from(value: Wrapper) -> Self {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let mut res: ExternalPtr<Wrapper> = ExternalPtr::new(value);
+                      res.set_attrib(class_symbol(), "Wrapper").unwrap();
+                      res.into()
+                  }
+              }
+          }
           #[automatically_derived]
           impl ::core::default::Default for Wrapper {
               #[inline]
@@ -2421,48 +2463,6 @@
                       hidden: false,
                   })
           }
-          impl TryFrom<Robj> for &Wrapper {
-              type Error = extendr_api::Error;
-              fn try_from(robj: Robj) -> extendr_api::Result<Self> {
-                  Self::try_from(&robj)
-              }
-          }
-          impl TryFrom<Robj> for &mut Wrapper {
-              type Error = extendr_api::Error;
-              fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
-                  Self::try_from(&mut robj)
-              }
-          }
-          impl TryFrom<&Robj> for &Wrapper {
-              type Error = extendr_api::Error;
-              fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
-                  use extendr_api::ExternalPtr;
-                  unsafe {
-                      let external_ptr: &ExternalPtr<Wrapper> = robj.try_into()?;
-                      external_ptr.try_addr()
-                  }
-              }
-          }
-          impl TryFrom<&mut Robj> for &mut Wrapper {
-              type Error = extendr_api::Error;
-              fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
-                  use extendr_api::ExternalPtr;
-                  unsafe {
-                      let external_ptr: &mut ExternalPtr<Wrapper> = robj.try_into()?;
-                      external_ptr.try_addr_mut()
-                  }
-              }
-          }
-          impl From<Wrapper> for Robj {
-              fn from(value: Wrapper) -> Self {
-                  use extendr_api::ExternalPtr;
-                  unsafe {
-                      let mut res: ExternalPtr<Wrapper> = ExternalPtr::new(value);
-                      res.set_attrib(class_symbol(), "Wrapper").unwrap();
-                      res.into()
-                  }
-              }
-          }
           #[allow(non_snake_case)]
           fn meta__Wrapper(impls: &mut Vec<extendr_api::metadata::Impl>) {
               let mut methods = Vec::new();
@@ -2755,126 +2755,793 @@
                       hidden: false,
                   })
           }
-          #[no_mangle]
-          #[allow(non_snake_case)]
-          pub fn get_externalptr_metadata() -> extendr_api::metadata::Metadata {
-              let mut functions = Vec::new();
-              let mut impls = Vec::new();
-              meta__create_numeric_externalptr(&mut functions);
-              meta__sum_integer_externalptr(&mut functions);
-              meta__Wrapper(&mut impls);
-              functions
-                  .push(extendr_api::metadata::Func {
-                      doc: "Metadata access function.",
-                      rust_name: "get_externalptr_metadata",
-                      mod_name: "get_externalptr_metadata",
-                      r_name: "get_externalptr_metadata",
-                      args: Vec::new(),
-                      return_type: "Metadata",
-                      func_ptr: wrap__get_externalptr_metadata as *const u8,
-                      hidden: true,
-                  });
-              functions
-                  .push(extendr_api::metadata::Func {
-                      doc: "Wrapper generator.",
-                      rust_name: "make_externalptr_wrappers",
-                      mod_name: "make_externalptr_wrappers",
-                      r_name: "make_externalptr_wrappers",
-                      args: <[_]>::into_vec(
-                          #[rustc_box]
-                          ::alloc::boxed::Box::new([
-                              extendr_api::metadata::Arg {
-                                  name: "use_symbols",
-                                  arg_type: "bool",
-                                  default: None,
-                              },
-                              extendr_api::metadata::Arg {
-                                  name: "package_name",
-                                  arg_type: "&str",
-                                  default: None,
-                              },
-                          ]),
-                      ),
-                      return_type: "String",
-                      func_ptr: wrap__make_externalptr_wrappers as *const u8,
-                      hidden: true,
-                  });
-              extendr_api::metadata::Metadata {
-                  name: "externalptr",
-                  functions,
-                  impls,
+          mod submod {
+              use super::*;
+              impl Wrapper {
+                  pub fn a_10(&self) -> i32 {
+                      self.a + 10
+                  }
               }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case)]
-          pub extern "C" fn wrap__get_externalptr_metadata() -> extendr_api::SEXP {
-              use extendr_api::GetSexp;
-              unsafe { extendr_api::Robj::from(get_externalptr_metadata()).get() }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__make_externalptr_wrappers(
-              use_symbols_sexp: extendr_api::SEXP,
-              package_name_sexp: extendr_api::SEXP,
-          ) -> extendr_api::SEXP {
-              unsafe {
+              #[no_mangle]
+              #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+              pub extern "C" fn wrap__Wrapper__a_10(
+                  _self: extendr_api::SEXP,
+              ) -> extendr_api::SEXP {
                   use extendr_api::robj::*;
-                  use extendr_api::GetSexp;
-                  let robj = Robj::from_sexp(use_symbols_sexp);
-                  let use_symbols: bool = <bool>::try_from(&robj).unwrap();
-                  let robj = Robj::from_sexp(package_name_sexp);
-                  let package_name: &str = <&str>::try_from(&robj).unwrap();
-                  extendr_api::Robj::from(
-                          get_externalptr_metadata()
-                              .make_r_wrappers(use_symbols, package_name)
-                              .unwrap(),
+                  let wrap_result_state: std::result::Result<
+                      std::result::Result<extendr_api::Robj, extendr_api::Error>,
+                      Box<dyn std::any::Any + Send>,
+                  > = unsafe {
+                      std::panic::catch_unwind(
+                          std::panic::AssertUnwindSafe(move || -> std::result::Result<
+                              extendr_api::Robj,
+                              extendr_api::Error,
+                          > {
+                              let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self);
+                              Ok(
+                                  extendr_api::Robj::from(
+                                      extendr_api::unwrap_or_throw_error(
+                                              <&Wrapper>::try_from(&_self_robj),
+                                          )
+                                          .a_10(),
+                                  ),
+                              )
+                          }),
                       )
-                      .get()
-              }
-          }
-          #[no_mangle]
-          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn R_init_externalptr_extendr(info: *mut extendr_api::DllInfo) {
-              unsafe { extendr_api::register_call_methods(info, get_externalptr_metadata()) };
-          }
-      }
-      mod graphic_device {
-          use extendr_api::{graphics::*, prelude::*};
-          pub(crate) struct MyDevice<'a> {
-              pub(crate) welcome_message: &'a str,
-          }
-          impl<'a> DeviceDriver for MyDevice<'a> {
-              fn activate(&mut self, _dd: DevDesc) {
-                  let welcome_message = self.welcome_message;
-                  print_r_output(
-                      ::alloc::__export::must_use({
-                          let res = ::alloc::fmt::format(
-                              format_args!("message from device: {0}", welcome_message),
+                  };
+                  match wrap_result_state {
+                      Ok(Ok(zz)) => {
+                          return unsafe { zz.get() };
+                      }
+                      Ok(Err(conversion_err)) => {
+                          let err_string = conversion_err.to_string();
+                          drop(conversion_err);
+                          extendr_api::throw_r_error(&err_string);
+                      }
+                      Err(unwind_err) => {
+                          drop(unwind_err);
+                          let err_string = ::alloc::__export::must_use({
+                              let res = ::alloc::fmt::format(
+                                  format_args!("User function panicked: {0}", "a_10"),
+                              );
+                              res
+                          });
+                          extendr_api::handle_panic(
+                              err_string.as_str(),
+                              || {
+                                  #[cold]
+                                  #[track_caller]
+                                  #[inline(never)]
+                                  const fn panic_cold_explicit() -> ! {
+                                      ::core::panicking::panic_explicit()
+                                  }
+                                  panic_cold_explicit();
+                              },
                           );
-                          res
-                      }),
-                  );
-                  print_r_output("\n");
+                      }
+                  }
+                  {
+                      ::core::panicking::panic_fmt(
+                          format_args!(
+                              "internal error: entered unreachable code: {0}",
+                              format_args!("internal extendr error, this should never happen."),
+                          ),
+                      );
+                  }
               }
-              fn close(&mut self, _dd: DevDesc) {
-                  print_r_output(
-                      ::alloc::__export::must_use({
-                          let res = ::alloc::fmt::format(format_args!("good bye..."));
-                          res
-                      }),
+              #[allow(non_snake_case)]
+              fn meta__Wrapper__a_10(metadata: &mut Vec<extendr_api::metadata::Func>) {
+                  let args = <[_]>::into_vec(
+                      #[rustc_box]
+                      ::alloc::boxed::Box::new([
+                          extendr_api::metadata::Arg {
+                              name: "self",
+                              arg_type: "Wrapper",
+                              default: None,
+                          },
+                      ]),
                   );
-                  print_r_output("\n");
+                  metadata
+                      .push(extendr_api::metadata::Func {
+                          doc: "",
+                          rust_name: "a_10",
+                          r_name: "a_10",
+                          mod_name: "a_10",
+                          args: args,
+                          return_type: "i32",
+                          func_ptr: wrap__Wrapper__a_10 as *const u8,
+                          hidden: false,
+                      })
               }
-          }
-      }
-      mod matrix {
-          use extendr_api::prelude::*;
-          fn fetch_dimnames(x: RMatrix<f64>) -> List {
-              x.get_dimnames()
-          }
+              #[allow(non_snake_case)]
+              fn meta__Wrapper(impls: &mut Vec<extendr_api::metadata::Impl>) {
+                  let mut methods = Vec::new();
+                  meta__Wrapper__a_10(&mut methods);
+                  impls
+                      .push(extendr_api::metadata::Impl {
+                          doc: "",
+                          name: "Wrapper",
+                          methods,
+                      });
+              }
+              #[no_mangle]
+              #[allow(non_snake_case)]
+              pub fn get_submod_metadata() -> extendr_api::metadata::Metadata {
+                  let mut functions = Vec::new();
+                  let mut impls = Vec::new();
+                  meta__Wrapper(&mut impls);
+                  functions
+                      .push(extendr_api::metadata::Func {
+                          doc: "Metadata access function.",
+                          rust_name: "get_submod_metadata",
+                          mod_name: "get_submod_metadata",
+                          r_name: "get_submod_metadata",
+                          args: Vec::new(),
+                          return_type: "Metadata",
+                          func_ptr: wrap__get_submod_metadata as *const u8,
+                          hidden: true,
+                      });
+                  functions
+                      .push(extendr_api::metadata::Func {
+                          doc: "Wrapper generator.",
+                          rust_name: "make_submod_wrappers",
+                          mod_name: "make_submod_wrappers",
+                          r_name: "make_submod_wrappers",
+                          args: <[_]>::into_vec(
+                              #[rustc_box]
+                              ::alloc::boxed::Box::new([
+                                  extendr_api::metadata::Arg {
+                                      name: "use_symbols",
+                                      arg_type: "bool",
+                                      default: None,
+                                  },
+                                  extendr_api::metadata::Arg {
+                                      name: "package_name",
+                                      arg_type: "&str",
+                                      default: None,
+                                  },
+                              ]),
+                          ),
+                          return_type: "String",
+                          func_ptr: wrap__make_submod_wrappers as *const u8,
+                          hidden: true,
+                      });
+                  extendr_api::metadata::Metadata {
+                      name: "submod",
+                      functions,
+                      impls,
+                  }
+              }
+              #[no_mangle]
+              #[allow(non_snake_case)]
+              pub extern "C" fn wrap__get_submod_metadata() -> extendr_api::SEXP {
+                  use extendr_api::GetSexp;
+                  unsafe { extendr_api::Robj::from(get_submod_metadata()).get() }
+              }
+              #[no_mangle]
+              #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+              pub extern "C" fn wrap__make_submod_wrappers(
+                  use_symbols_sexp: extendr_api::SEXP,
+                  package_name_sexp: extendr_api::SEXP,
+              ) -> extendr_api::SEXP {
+                  unsafe {
+                      use extendr_api::robj::*;
+                      use extendr_api::GetSexp;
+                      let robj = Robj::from_sexp(use_symbols_sexp);
+                      let use_symbols: bool = <bool>::try_from(&robj).unwrap();
+                      let robj = Robj::from_sexp(package_name_sexp);
+                      let package_name: &str = <&str>::try_from(&robj).unwrap();
+                      extendr_api::Robj::from(
+                              get_submod_metadata()
+                                  .make_r_wrappers(use_symbols, package_name)
+                                  .unwrap(),
+                          )
+                          .get()
+                  }
+              }
+              #[no_mangle]
+              #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+              pub extern "C" fn R_init_submod_extendr(info: *mut extendr_api::DllInfo) {
+                  unsafe { extendr_api::register_call_methods(info, get_submod_metadata()) };
+              }
+          }
+          enum Animal {
+              Cat,
+              Dog,
+          }
+          impl TryFrom<Robj> for &Animal {
+              type Error = extendr_api::Error;
+              fn try_from(robj: Robj) -> extendr_api::Result<Self> {
+                  Self::try_from(&robj)
+              }
+          }
+          impl TryFrom<Robj> for &mut Animal {
+              type Error = extendr_api::Error;
+              fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
+                  Self::try_from(&mut robj)
+              }
+          }
+          impl TryFrom<&Robj> for &Animal {
+              type Error = extendr_api::Error;
+              fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let external_ptr: &ExternalPtr<Animal> = robj.try_into()?;
+                      external_ptr.try_addr()
+                  }
+              }
+          }
+          impl TryFrom<&mut Robj> for &mut Animal {
+              type Error = extendr_api::Error;
+              fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let external_ptr: &mut ExternalPtr<Animal> = robj.try_into()?;
+                      external_ptr.try_addr_mut()
+                  }
+              }
+          }
+          impl From<Animal> for Robj {
+              fn from(value: Animal) -> Self {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let mut res: ExternalPtr<Animal> = ExternalPtr::new(value);
+                      res.set_attrib(class_symbol(), "Animal").unwrap();
+                      res.into()
+                  }
+              }
+          }
+          impl Animal {
+              pub fn new_dog() -> Self {
+                  Animal::Dog
+              }
+              pub fn new_cat() -> Self {
+                  Animal::Cat
+              }
+              pub fn speak(&self) -> Strings {
+                  match self {
+                      Animal::Dog => Strings::from("woof"),
+                      Animal::Cat => Strings::from("meow"),
+                  }
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__Animal__new_dog() -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<extendr_api::Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  std::panic::catch_unwind(
+                      std::panic::AssertUnwindSafe(move || -> std::result::Result<
+                          extendr_api::Robj,
+                          extendr_api::Error,
+                      > { Ok(extendr_api::Robj::from(<Animal>::new_dog())) }),
+                  )
+              };
+              match wrap_result_state {
+                  Ok(Ok(zz)) => {
+                      return unsafe { zz.get() };
+                  }
+                  Ok(Err(conversion_err)) => {
+                      let err_string = conversion_err.to_string();
+                      drop(conversion_err);
+                      extendr_api::throw_r_error(&err_string);
+                  }
+                  Err(unwind_err) => {
+                      drop(unwind_err);
+                      let err_string = ::alloc::__export::must_use({
+                          let res = ::alloc::fmt::format(
+                              format_args!("User function panicked: {0}", "new_dog"),
+                          );
+                          res
+                      });
+                      extendr_api::handle_panic(
+                          err_string.as_str(),
+                          || {
+                              #[cold]
+                              #[track_caller]
+                              #[inline(never)]
+                              const fn panic_cold_explicit() -> ! {
+                                  ::core::panicking::panic_explicit()
+                              }
+                              panic_cold_explicit();
+                          },
+                      );
+                  }
+              }
+              {
+                  ::core::panicking::panic_fmt(
+                      format_args!(
+                          "internal error: entered unreachable code: {0}",
+                          format_args!("internal extendr error, this should never happen."),
+                      ),
+                  );
+              }
+          }
+          #[allow(non_snake_case)]
+          fn meta__Animal__new_dog(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let args = ::alloc::vec::Vec::new();
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: "",
+                      rust_name: "new_dog",
+                      r_name: "new_dog",
+                      mod_name: "new_dog",
+                      args: args,
+                      return_type: "Self",
+                      func_ptr: wrap__Animal__new_dog as *const u8,
+                      hidden: false,
+                  })
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__Animal__new_cat() -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<extendr_api::Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  std::panic::catch_unwind(
+                      std::panic::AssertUnwindSafe(move || -> std::result::Result<
+                          extendr_api::Robj,
+                          extendr_api::Error,
+                      > { Ok(extendr_api::Robj::from(<Animal>::new_cat())) }),
+                  )
+              };
+              match wrap_result_state {
+                  Ok(Ok(zz)) => {
+                      return unsafe { zz.get() };
+                  }
+                  Ok(Err(conversion_err)) => {
+                      let err_string = conversion_err.to_string();
+                      drop(conversion_err);
+                      extendr_api::throw_r_error(&err_string);
+                  }
+                  Err(unwind_err) => {
+                      drop(unwind_err);
+                      let err_string = ::alloc::__export::must_use({
+                          let res = ::alloc::fmt::format(
+                              format_args!("User function panicked: {0}", "new_cat"),
+                          );
+                          res
+                      });
+                      extendr_api::handle_panic(
+                          err_string.as_str(),
+                          || {
+                              #[cold]
+                              #[track_caller]
+                              #[inline(never)]
+                              const fn panic_cold_explicit() -> ! {
+                                  ::core::panicking::panic_explicit()
+                              }
+                              panic_cold_explicit();
+                          },
+                      );
+                  }
+              }
+              {
+                  ::core::panicking::panic_fmt(
+                      format_args!(
+                          "internal error: entered unreachable code: {0}",
+                          format_args!("internal extendr error, this should never happen."),
+                      ),
+                  );
+              }
+          }
+          #[allow(non_snake_case)]
+          fn meta__Animal__new_cat(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let args = ::alloc::vec::Vec::new();
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: "",
+                      rust_name: "new_cat",
+                      r_name: "new_cat",
+                      mod_name: "new_cat",
+                      args: args,
+                      return_type: "Self",
+                      func_ptr: wrap__Animal__new_cat as *const u8,
+                      hidden: false,
+                  })
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__Animal__speak(
+              _self: extendr_api::SEXP,
+          ) -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<extendr_api::Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  std::panic::catch_unwind(
+                      std::panic::AssertUnwindSafe(move || -> std::result::Result<
+                          extendr_api::Robj,
+                          extendr_api::Error,
+                      > {
+                          let mut _self_robj = extendr_api::robj::Robj::from_sexp(_self);
+                          Ok(
+                              extendr_api::Robj::from(
+                                  extendr_api::unwrap_or_throw_error(
+                                          <&Animal>::try_from(&_self_robj),
+                                      )
+                                      .speak(),
+                              ),
+                          )
+                      }),
+                  )
+              };
+              match wrap_result_state {
+                  Ok(Ok(zz)) => {
+                      return unsafe { zz.get() };
+                  }
+                  Ok(Err(conversion_err)) => {
+                      let err_string = conversion_err.to_string();
+                      drop(conversion_err);
+                      extendr_api::throw_r_error(&err_string);
+                  }
+                  Err(unwind_err) => {
+                      drop(unwind_err);
+                      let err_string = ::alloc::__export::must_use({
+                          let res = ::alloc::fmt::format(
+                              format_args!("User function panicked: {0}", "speak"),
+                          );
+                          res
+                      });
+                      extendr_api::handle_panic(
+                          err_string.as_str(),
+                          || {
+                              #[cold]
+                              #[track_caller]
+                              #[inline(never)]
+                              const fn panic_cold_explicit() -> ! {
+                                  ::core::panicking::panic_explicit()
+                              }
+                              panic_cold_explicit();
+                          },
+                      );
+                  }
+              }
+              {
+                  ::core::panicking::panic_fmt(
+                      format_args!(
+                          "internal error: entered unreachable code: {0}",
+                          format_args!("internal extendr error, this should never happen."),
+                      ),
+                  );
+              }
+          }
+          #[allow(non_snake_case)]
+          fn meta__Animal__speak(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "self",
+                          arg_type: "Animal",
+                          default: None,
+                      },
+                  ]),
+              );
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: "",
+                      rust_name: "speak",
+                      r_name: "speak",
+                      mod_name: "speak",
+                      args: args,
+                      return_type: "Strings",
+                      func_ptr: wrap__Animal__speak as *const u8,
+                      hidden: false,
+                  })
+          }
+          #[allow(non_snake_case)]
+          fn meta__Animal(impls: &mut Vec<extendr_api::metadata::Impl>) {
+              let mut methods = Vec::new();
+              meta__Animal__new_dog(&mut methods);
+              meta__Animal__new_cat(&mut methods);
+              meta__Animal__speak(&mut methods);
+              impls
+                  .push(extendr_api::metadata::Impl {
+                      doc: "",
+                      name: "Animal",
+                      methods,
+                  });
+          }
+          #[no_mangle]
+          #[allow(non_snake_case)]
+          pub fn get_externalptr_metadata() -> extendr_api::metadata::Metadata {
+              let mut functions = Vec::new();
+              let mut impls = Vec::new();
+              meta__create_numeric_externalptr(&mut functions);
+              meta__sum_integer_externalptr(&mut functions);
+              meta__Wrapper(&mut impls);
+              meta__Animal(&mut impls);
+              functions.extend(submod::get_submod_metadata().functions);
+              impls.extend(submod::get_submod_metadata().impls);
+              functions
+                  .push(extendr_api::metadata::Func {
+                      doc: "Metadata access function.",
+                      rust_name: "get_externalptr_metadata",
+                      mod_name: "get_externalptr_metadata",
+                      r_name: "get_externalptr_metadata",
+                      args: Vec::new(),
+                      return_type: "Metadata",
+                      func_ptr: wrap__get_externalptr_metadata as *const u8,
+                      hidden: true,
+                  });
+              functions
+                  .push(extendr_api::metadata::Func {
+                      doc: "Wrapper generator.",
+                      rust_name: "make_externalptr_wrappers",
+                      mod_name: "make_externalptr_wrappers",
+                      r_name: "make_externalptr_wrappers",
+                      args: <[_]>::into_vec(
+                          #[rustc_box]
+                          ::alloc::boxed::Box::new([
+                              extendr_api::metadata::Arg {
+                                  name: "use_symbols",
+                                  arg_type: "bool",
+                                  default: None,
+                              },
+                              extendr_api::metadata::Arg {
+                                  name: "package_name",
+                                  arg_type: "&str",
+                                  default: None,
+                              },
+                          ]),
+                      ),
+                      return_type: "String",
+                      func_ptr: wrap__make_externalptr_wrappers as *const u8,
+                      hidden: true,
+                  });
+              extendr_api::metadata::Metadata {
+                  name: "externalptr",
+                  functions,
+                  impls,
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case)]
+          pub extern "C" fn wrap__get_externalptr_metadata() -> extendr_api::SEXP {
+              use extendr_api::GetSexp;
+              unsafe { extendr_api::Robj::from(get_externalptr_metadata()).get() }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__make_externalptr_wrappers(
+              use_symbols_sexp: extendr_api::SEXP,
+              package_name_sexp: extendr_api::SEXP,
+          ) -> extendr_api::SEXP {
+              unsafe {
+                  use extendr_api::robj::*;
+                  use extendr_api::GetSexp;
+                  let robj = Robj::from_sexp(use_symbols_sexp);
+                  let use_symbols: bool = <bool>::try_from(&robj).unwrap();
+                  let robj = Robj::from_sexp(package_name_sexp);
+                  let package_name: &str = <&str>::try_from(&robj).unwrap();
+                  extendr_api::Robj::from(
+                          get_externalptr_metadata()
+                              .make_r_wrappers(use_symbols, package_name)
+                              .unwrap(),
+                      )
+                      .get()
+              }
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn R_init_externalptr_extendr(info: *mut extendr_api::DllInfo) {
+              unsafe { extendr_api::register_call_methods(info, get_externalptr_metadata()) };
+          }
+      }
+      mod graphic_device {
+          use extendr_api::{graphics::*, prelude::*};
+          pub(crate) struct MyDevice<'a> {
+              pub(crate) welcome_message: &'a str,
+          }
+          impl<'a> DeviceDriver for MyDevice<'a> {
+              fn activate(&mut self, _dd: DevDesc) {
+                  let welcome_message = self.welcome_message;
+                  print_r_output(
+                      ::alloc::__export::must_use({
+                          let res = ::alloc::fmt::format(
+                              format_args!("message from device: {0}", welcome_message),
+                          );
+                          res
+                      }),
+                  );
+                  print_r_output("\n");
+              }
+              fn close(&mut self, _dd: DevDesc) {
+                  print_r_output(
+                      ::alloc::__export::must_use({
+                          let res = ::alloc::fmt::format(format_args!("good bye..."));
+                          res
+                      }),
+                  );
+                  print_r_output("\n");
+              }
+          }
+      }
+      mod matrix {
+          use extendr_api::prelude::*;
+          fn fetch_dimnames(x: RMatrix<f64>) -> List {
+              x.get_dimnames()
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__fetch_dimnames(x: extendr_api::SEXP) -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<extendr_api::Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  std::panic::catch_unwind(
+                      std::panic::AssertUnwindSafe(move || -> std::result::Result<
+                          extendr_api::Robj,
+                          extendr_api::Error,
+                      > {
+                          let _x_robj = extendr_api::robj::Robj::from_sexp(x);
+                          Ok(extendr_api::Robj::from(fetch_dimnames(_x_robj.try_into()?)))
+                      }),
+                  )
+              };
+              match wrap_result_state {
+                  Ok(Ok(zz)) => {
+                      return unsafe { zz.get() };
+                  }
+                  Ok(Err(conversion_err)) => {
+                      let err_string = conversion_err.to_string();
+                      drop(conversion_err);
+                      extendr_api::throw_r_error(&err_string);
+                  }
+                  Err(unwind_err) => {
+                      drop(unwind_err);
+                      let err_string = ::alloc::__export::must_use({
+                          let res = ::alloc::fmt::format(
+                              format_args!("User function panicked: {0}", "fetch_dimnames"),
+                          );
+                          res
+                      });
+                      extendr_api::handle_panic(
+                          err_string.as_str(),
+                          || {
+                              #[cold]
+                              #[track_caller]
+                              #[inline(never)]
+                              const fn panic_cold_explicit() -> ! {
+                                  ::core::panicking::panic_explicit()
+                              }
+                              panic_cold_explicit();
+                          },
+                      );
+                  }
+              }
+              {
+                  ::core::panicking::panic_fmt(
+                      format_args!(
+                          "internal error: entered unreachable code: {0}",
+                          format_args!("internal extendr error, this should never happen."),
+                      ),
+                  );
+              }
+          }
+          #[allow(non_snake_case)]
+          fn meta__fetch_dimnames(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "x",
+                          arg_type: "RMatrix",
+                          default: None,
+                      },
+                  ]),
+              );
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: "",
+                      rust_name: "fetch_dimnames",
+                      r_name: "fetch_dimnames",
+                      mod_name: "fetch_dimnames",
+                      args: args,
+                      return_type: "List",
+                      func_ptr: wrap__fetch_dimnames as *const u8,
+                      hidden: false,
+                  })
+          }
+          fn fetch_rownames(x: RMatrix<f64>) -> Option<Strings> {
+              x.get_rownames()
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__fetch_rownames(x: extendr_api::SEXP) -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<extendr_api::Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  std::panic::catch_unwind(
+                      std::panic::AssertUnwindSafe(move || -> std::result::Result<
+                          extendr_api::Robj,
+                          extendr_api::Error,
+                      > {
+                          let _x_robj = extendr_api::robj::Robj::from_sexp(x);
+                          Ok(extendr_api::Robj::from(fetch_rownames(_x_robj.try_into()?)))
+                      }),
+                  )
+              };
+              match wrap_result_state {
+                  Ok(Ok(zz)) => {
+                      return unsafe { zz.get() };
+                  }
+                  Ok(Err(conversion_err)) => {
+                      let err_string = conversion_err.to_string();
+                      drop(conversion_err);
+                      extendr_api::throw_r_error(&err_string);
+                  }
+                  Err(unwind_err) => {
+                      drop(unwind_err);
+                      let err_string = ::alloc::__export::must_use({
+                          let res = ::alloc::fmt::format(
+                              format_args!("User function panicked: {0}", "fetch_rownames"),
+                          );
+                          res
+                      });
+                      extendr_api::handle_panic(
+                          err_string.as_str(),
+                          || {
+                              #[cold]
+                              #[track_caller]
+                              #[inline(never)]
+                              const fn panic_cold_explicit() -> ! {
+                                  ::core::panicking::panic_explicit()
+                              }
+                              panic_cold_explicit();
+                          },
+                      );
+                  }
+              }
+              {
+                  ::core::panicking::panic_fmt(
+                      format_args!(
+                          "internal error: entered unreachable code: {0}",
+                          format_args!("internal extendr error, this should never happen."),
+                      ),
+                  );
+              }
+          }
+          #[allow(non_snake_case)]
+          fn meta__fetch_rownames(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "x",
+                          arg_type: "RMatrix",
+                          default: None,
+                      },
+                  ]),
+              );
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: "",
+                      rust_name: "fetch_rownames",
+                      r_name: "fetch_rownames",
+                      mod_name: "fetch_rownames",
+                      args: args,
+                      return_type: "Option",
+                      func_ptr: wrap__fetch_rownames as *const u8,
+                      hidden: false,
+                  })
+          }
+          fn fetch_colnames(x: RMatrix<f64>) -> Option<Strings> {
+              x.get_colnames()
+          }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__fetch_dimnames(x: extendr_api::SEXP) -> extendr_api::SEXP {
+          pub extern "C" fn wrap__fetch_colnames(x: extendr_api::SEXP) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<extendr_api::Robj, extendr_api::Error>,
@@ -2886,7 +3553,7 @@
                           extendr_api::Error,
                       > {
                           let _x_robj = extendr_api::robj::Robj::from_sexp(x);
-                          Ok(extendr_api::Robj::from(fetch_dimnames(_x_robj.try_into()?)))
+                          Ok(extendr_api::Robj::from(fetch_colnames(_x_robj.try_into()?)))
                       }),
                   )
               };
@@ -2903,7 +3570,7 @@
                       drop(unwind_err);
                       let err_string = ::alloc::__export::must_use({
                           let res = ::alloc::fmt::format(
-                              format_args!("User function panicked: {0}", "fetch_dimnames"),
+                              format_args!("User function panicked: {0}", "fetch_colnames"),
                           );
                           res
                       });
@@ -2931,7 +3598,7 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__fetch_dimnames(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__fetch_colnames(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
@@ -2945,21 +3612,27 @@
               metadata
                   .push(extendr_api::metadata::Func {
                       doc: "",
-                      rust_name: "fetch_dimnames",
-                      r_name: "fetch_dimnames",
-                      mod_name: "fetch_dimnames",
+                      rust_name: "fetch_colnames",
+                      r_name: "fetch_colnames",
+                      mod_name: "fetch_colnames",
                       args: args,
-                      return_type: "List",
-                      func_ptr: wrap__fetch_dimnames as *const u8,
+                      return_type: "Option",
+                      func_ptr: wrap__fetch_colnames as *const u8,
                       hidden: false,
                   })
           }
-          fn fetch_rownames(x: RMatrix<f64>) -> Option<Strings> {
-              x.get_rownames()
+          fn change_dimnames(mut x: RMatrix<f64>) -> Robj {
+              let rownames = Strings::from_values(["AA", "BB", "CC"]);
+              x.set_dimnames(
+                  extendr_api::List::from_values(
+                      &[extendr_api::Robj::from(rownames), extendr_api::Robj::from(NULL)],
+                  ),
+              );
+              x.to_owned()
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__fetch_rownames(x: extendr_api::SEXP) -> extendr_api::SEXP {
+          pub extern "C" fn wrap__change_dimnames(x: extendr_api::SEXP) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<extendr_api::Robj, extendr_api::Error>,
@@ -2971,7 +3644,7 @@
                           extendr_api::Error,
                       > {
                           let _x_robj = extendr_api::robj::Robj::from_sexp(x);
-                          Ok(extendr_api::Robj::from(fetch_rownames(_x_robj.try_into()?)))
+                          Ok(extendr_api::Robj::from(change_dimnames(_x_robj.try_into()?)))
                       }),
                   )
               };
@@ -2988,7 +3661,7 @@
                       drop(unwind_err);
                       let err_string = ::alloc::__export::must_use({
                           let res = ::alloc::fmt::format(
-                              format_args!("User function panicked: {0}", "fetch_rownames"),
+                              format_args!("User function panicked: {0}", "change_dimnames"),
                           );
                           res
                       });
@@ -3016,7 +3689,7 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__fetch_rownames(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__change_dimnames(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
@@ -3030,21 +3703,21 @@
               metadata
                   .push(extendr_api::metadata::Func {
                       doc: "",
-                      rust_name: "fetch_rownames",
-                      r_name: "fetch_rownames",
-                      mod_name: "fetch_rownames",
+                      rust_name: "change_dimnames",
+                      r_name: "change_dimnames",
+                      mod_name: "change_dimnames",
                       args: args,
-                      return_type: "Option",
-                      func_ptr: wrap__fetch_rownames as *const u8,
+                      return_type: "Robj",
+                      func_ptr: wrap__change_dimnames as *const u8,
                       hidden: false,
                   })
           }
-          fn fetch_colnames(x: RMatrix<f64>) -> Option<Strings> {
-              x.get_colnames()
+          fn matrix_3d_return(x: RMatrix3D<f64>) -> RMatrix3D<f64> {
+              x
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__fetch_colnames(x: extendr_api::SEXP) -> extendr_api::SEXP {
+          pub extern "C" fn wrap__matrix_3d_return(x: extendr_api::SEXP) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<extendr_api::Robj, extendr_api::Error>,
@@ -3056,7 +3729,7 @@
                           extendr_api::Error,
                       > {
                           let _x_robj = extendr_api::robj::Robj::from_sexp(x);
-                          Ok(extendr_api::Robj::from(fetch_colnames(_x_robj.try_into()?)))
+                          Ok(extendr_api::Robj::from(matrix_3d_return(_x_robj.try_into()?)))
                       }),
                   )
               };
@@ -3073,7 +3746,7 @@
                       drop(unwind_err);
                       let err_string = ::alloc::__export::must_use({
                           let res = ::alloc::fmt::format(
-                              format_args!("User function panicked: {0}", "fetch_colnames"),
+                              format_args!("User function panicked: {0}", "matrix_3d_return"),
                           );
                           res
                       });
@@ -3101,13 +3774,13 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__fetch_colnames(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__matrix_3d_return(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
                       extendr_api::metadata::Arg {
                           name: "x",
-                          arg_type: "RMatrix",
+                          arg_type: "RMatrix3D",
                           default: None,
                       },
                   ]),
@@ -3115,27 +3788,106 @@
               metadata
                   .push(extendr_api::metadata::Func {
                       doc: "",
-                      rust_name: "fetch_colnames",
-                      r_name: "fetch_colnames",
-                      mod_name: "fetch_colnames",
+                      rust_name: "matrix_3d_return",
+                      r_name: "matrix_3d_return",
+                      mod_name: "matrix_3d_return",
                       args: args,
-                      return_type: "Option",
-                      func_ptr: wrap__fetch_colnames as *const u8,
+                      return_type: "RMatrix3D",
+                      func_ptr: wrap__matrix_3d_return as *const u8,
                       hidden: false,
                   })
           }
-          fn change_dimnames(mut x: RMatrix<f64>) -> Robj {
-              let rownames = Strings::from_values(["AA", "BB", "CC"]);
-              x.set_dimnames(
-                  extendr_api::List::from_values(
-                      &[extendr_api::Robj::from(rownames), extendr_api::Robj::from(NULL)],
-                  ),
+          fn matrix_4d_return(x: RMatrix4D<f64>) -> RMatrix4D<f64> {
+              x
+          }
+          #[no_mangle]
+          #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
+          pub extern "C" fn wrap__matrix_4d_return(x: extendr_api::SEXP) -> extendr_api::SEXP {
+              use extendr_api::robj::*;
+              let wrap_result_state: std::result::Result<
+                  std::result::Result<extendr_api::Robj, extendr_api::Error>,
+                  Box<dyn std::any::Any + Send>,
+              > = unsafe {
+                  std::panic::catch_unwind(
+                      std::panic::AssertUnwindSafe(move || -> std::result::Result<
+                          extendr_api::Robj,
+                          extendr_api::Error,
+                      > {
+                          let _x_robj = extendr_api::robj::Robj::from_sexp(x);
+                          Ok(extendr_api::Robj::from(matrix_4d_return(_x_robj.try_into()?)))
+                      }),
+                  )
+              };
+              match wrap_result_state {
+                  Ok(Ok(zz)) => {
+                      return unsafe { zz.get() };
+                  }
+                  Ok(Err(conversion_err)) => {
+                      let err_string = conversion_err.to_string();
+                      drop(conversion_err);
+                      extendr_api::throw_r_error(&err_string);
+                  }
+                  Err(unwind_err) => {
+                      drop(unwind_err);
+                      let err_string = ::alloc::__export::must_use({
+                          let res = ::alloc::fmt::format(
+                              format_args!("User function panicked: {0}", "matrix_4d_return"),
+                          );
+                          res
+                      });
+                      extendr_api::handle_panic(
+                          err_string.as_str(),
+                          || {
+                              #[cold]
+                              #[track_caller]
+                              #[inline(never)]
+                              const fn panic_cold_explicit() -> ! {
+                                  ::core::panicking::panic_explicit()
+                              }
+                              panic_cold_explicit();
+                          },
+                      );
+                  }
+              }
+              {
+                  ::core::panicking::panic_fmt(
+                      format_args!(
+                          "internal error: entered unreachable code: {0}",
+                          format_args!("internal extendr error, this should never happen."),
+                      ),
+                  );
+              }
+          }
+          #[allow(non_snake_case)]
+          fn meta__matrix_4d_return(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let args = <[_]>::into_vec(
+                  #[rustc_box]
+                  ::alloc::boxed::Box::new([
+                      extendr_api::metadata::Arg {
+                          name: "x",
+                          arg_type: "RMatrix4D",
+                          default: None,
+                      },
+                  ]),
               );
-              x.to_owned()
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: "",
+                      rust_name: "matrix_4d_return",
+                      r_name: "matrix_4d_return",
+                      mod_name: "matrix_4d_return",
+                      args: args,
+                      return_type: "RMatrix4D",
+                      func_ptr: wrap__matrix_4d_return as *const u8,
+                      hidden: false,
+                  })
+          }
+          fn matrix_5d_return(x: RMatrix5D<f64>) -> RMatrix5D<f64> {
+              x
           }
           #[no_mangle]
           #[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
-          pub extern "C" fn wrap__change_dimnames(x: extendr_api::SEXP) -> extendr_api::SEXP {
+          pub extern "C" fn wrap__matrix_5d_return(x: extendr_api::SEXP) -> extendr_api::SEXP {
               use extendr_api::robj::*;
               let wrap_result_state: std::result::Result<
                   std::result::Result<extendr_api::Robj, extendr_api::Error>,
@@ -3147,7 +3899,7 @@
                           extendr_api::Error,
                       > {
                           let _x_robj = extendr_api::robj::Robj::from_sexp(x);
-                          Ok(extendr_api::Robj::from(change_dimnames(_x_robj.try_into()?)))
+                          Ok(extendr_api::Robj::from(matrix_5d_return(_x_robj.try_into()?)))
                       }),
                   )
               };
@@ -3164,7 +3916,7 @@
                       drop(unwind_err);
                       let err_string = ::alloc::__export::must_use({
                           let res = ::alloc::fmt::format(
-                              format_args!("User function panicked: {0}", "change_dimnames"),
+                              format_args!("User function panicked: {0}", "matrix_5d_return"),
                           );
                           res
                       });
@@ -3192,13 +3944,13 @@
               }
           }
           #[allow(non_snake_case)]
-          fn meta__change_dimnames(metadata: &mut Vec<extendr_api::metadata::Func>) {
+          fn meta__matrix_5d_return(metadata: &mut Vec<extendr_api::metadata::Func>) {
               let args = <[_]>::into_vec(
                   #[rustc_box]
                   ::alloc::boxed::Box::new([
                       extendr_api::metadata::Arg {
                           name: "x",
-                          arg_type: "RMatrix",
+                          arg_type: "RMatrix5D",
                           default: None,
                       },
                   ]),
@@ -3206,12 +3958,12 @@
               metadata
                   .push(extendr_api::metadata::Func {
                       doc: "",
-                      rust_name: "change_dimnames",
-                      r_name: "change_dimnames",
-                      mod_name: "change_dimnames",
+                      rust_name: "matrix_5d_return",
+                      r_name: "matrix_5d_return",
+                      mod_name: "matrix_5d_return",
                       args: args,
-                      return_type: "Robj",
-                      func_ptr: wrap__change_dimnames as *const u8,
+                      return_type: "RMatrix5D",
+                      func_ptr: wrap__matrix_5d_return as *const u8,
                       hidden: false,
                   })
           }
@@ -3224,6 +3976,9 @@
               meta__fetch_colnames(&mut functions);
               meta__fetch_rownames(&mut functions);
               meta__change_dimnames(&mut functions);
+              meta__matrix_3d_return(&mut functions);
+              meta__matrix_4d_return(&mut functions);
+              meta__matrix_5d_return(&mut functions);
               functions
                   .push(extendr_api::metadata::Func {
                       doc: "Metadata access function.",
@@ -5510,23 +6265,65 @@
                   );
               }
           }
-          #[allow(non_snake_case)]
-          fn meta__hello_submodule(metadata: &mut Vec<extendr_api::metadata::Func>) {
-              let args = ::alloc::vec::Vec::new();
-              metadata
-                  .push(extendr_api::metadata::Func {
-                      doc: " Return string `\"Hello world!\"` to R.\n @export",
-                      rust_name: "hello_submodule",
-                      r_name: "hello_submodule",
-                      mod_name: "hello_submodule",
-                      args: args,
-                      return_type: "str",
-                      func_ptr: wrap__hello_submodule as *const u8,
-                      hidden: false,
-                  })
-          }
-          struct MySubmoduleClass {
-              a: i32,
+          #[allow(non_snake_case)]
+          fn meta__hello_submodule(metadata: &mut Vec<extendr_api::metadata::Func>) {
+              let args = ::alloc::vec::Vec::new();
+              metadata
+                  .push(extendr_api::metadata::Func {
+                      doc: " Return string `\"Hello world!\"` to R.\n @export",
+                      rust_name: "hello_submodule",
+                      r_name: "hello_submodule",
+                      mod_name: "hello_submodule",
+                      args: args,
+                      return_type: "str",
+                      func_ptr: wrap__hello_submodule as *const u8,
+                      hidden: false,
+                  })
+          }
+          struct MySubmoduleClass {
+              a: i32,
+          }
+          impl TryFrom<Robj> for &MySubmoduleClass {
+              type Error = extendr_api::Error;
+              fn try_from(robj: Robj) -> extendr_api::Result<Self> {
+                  Self::try_from(&robj)
+              }
+          }
+          impl TryFrom<Robj> for &mut MySubmoduleClass {
+              type Error = extendr_api::Error;
+              fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
+                  Self::try_from(&mut robj)
+              }
+          }
+          impl TryFrom<&Robj> for &MySubmoduleClass {
+              type Error = extendr_api::Error;
+              fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let external_ptr: &ExternalPtr<MySubmoduleClass> = robj.try_into()?;
+                      external_ptr.try_addr()
+                  }
+              }
+          }
+          impl TryFrom<&mut Robj> for &mut MySubmoduleClass {
+              type Error = extendr_api::Error;
+              fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let external_ptr: &mut ExternalPtr<MySubmoduleClass> = robj.try_into()?;
+                      external_ptr.try_addr_mut()
+                  }
+              }
+          }
+          impl From<MySubmoduleClass> for Robj {
+              fn from(value: MySubmoduleClass) -> Self {
+                  use extendr_api::ExternalPtr;
+                  unsafe {
+                      let mut res: ExternalPtr<MySubmoduleClass> = ExternalPtr::new(value);
+                      res.set_attrib(class_symbol(), "MySubmoduleClass").unwrap();
+                      res.into()
+                  }
+              }
           }
           #[automatically_derived]
           impl ::core::default::Default for MySubmoduleClass {
@@ -5840,48 +6637,6 @@
                       hidden: false,
                   })
           }
-          impl TryFrom<Robj> for &MySubmoduleClass {
-              type Error = extendr_api::Error;
-              fn try_from(robj: Robj) -> extendr_api::Result<Self> {
-                  Self::try_from(&robj)
-              }
-          }
-          impl TryFrom<Robj> for &mut MySubmoduleClass {
-              type Error = extendr_api::Error;
-              fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
-                  Self::try_from(&mut robj)
-              }
-          }
-          impl TryFrom<&Robj> for &MySubmoduleClass {
-              type Error = extendr_api::Error;
-              fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
-                  use extendr_api::ExternalPtr;
-                  unsafe {
-                      let external_ptr: &ExternalPtr<MySubmoduleClass> = robj.try_into()?;
-                      external_ptr.try_addr()
-                  }
-              }
-          }
-          impl TryFrom<&mut Robj> for &mut MySubmoduleClass {
-              type Error = extendr_api::Error;
-              fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
-                  use extendr_api::ExternalPtr;
-                  unsafe {
-                      let external_ptr: &mut ExternalPtr<MySubmoduleClass> = robj.try_into()?;
-                      external_ptr.try_addr_mut()
-                  }
-              }
-          }
-          impl From<MySubmoduleClass> for Robj {
-              fn from(value: MySubmoduleClass) -> Self {
-                  use extendr_api::ExternalPtr;
-                  unsafe {
-                      let mut res: ExternalPtr<MySubmoduleClass> = ExternalPtr::new(value);
-                      res.set_attrib(class_symbol(), "MySubmoduleClass").unwrap();
-                      res.into()
-                  }
-              }
-          }
           #[allow(non_snake_case)]
           fn meta__MySubmoduleClass(impls: &mut Vec<extendr_api::metadata::Impl>) {
               let mut methods = Vec::new();
@@ -9734,6 +10489,48 @@
       struct MyClass {
           a: i32,
       }
+      impl TryFrom<Robj> for &MyClass {
+          type Error = extendr_api::Error;
+          fn try_from(robj: Robj) -> extendr_api::Result<Self> {
+              Self::try_from(&robj)
+          }
+      }
+      impl TryFrom<Robj> for &mut MyClass {
+          type Error = extendr_api::Error;
+          fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
+              Self::try_from(&mut robj)
+          }
+      }
+      impl TryFrom<&Robj> for &MyClass {
+          type Error = extendr_api::Error;
+          fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
+              use extendr_api::ExternalPtr;
+              unsafe {
+                  let external_ptr: &ExternalPtr<MyClass> = robj.try_into()?;
+                  external_ptr.try_addr()
+              }
+          }
+      }
+      impl TryFrom<&mut Robj> for &mut MyClass {
+          type Error = extendr_api::Error;
+          fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
+              use extendr_api::ExternalPtr;
+              unsafe {
+                  let external_ptr: &mut ExternalPtr<MyClass> = robj.try_into()?;
+                  external_ptr.try_addr_mut()
+              }
+          }
+      }
+      impl From<MyClass> for Robj {
+          fn from(value: MyClass) -> Self {
+              use extendr_api::ExternalPtr;
+              unsafe {
+                  let mut res: ExternalPtr<MyClass> = ExternalPtr::new(value);
+                  res.set_attrib(class_symbol(), "MyClass").unwrap();
+                  res.into()
+              }
+          }
+      }
       #[automatically_derived]
       impl ::core::default::Default for MyClass {
           #[inline]
@@ -10311,65 +11108,65 @@
                   hidden: false,
               })
       }
-      impl TryFrom<Robj> for &MyClass {
+      #[allow(non_snake_case)]
+      fn meta__MyClass(impls: &mut Vec<extendr_api::metadata::Impl>) {
+          let mut methods = Vec::new();
+          meta__MyClass__new(&mut methods);
+          meta__MyClass__set_a(&mut methods);
+          meta__MyClass__a(&mut methods);
+          meta__MyClass__me(&mut methods);
+          meta__MyClass__restore_from_robj(&mut methods);
+          meta__MyClass__get_default_value(&mut methods);
+          impls
+              .push(extendr_api::metadata::Impl {
+                  doc: " Class for testing (exported)\n @examples\n x <- MyClass$new()\n x$a()\n x$set_a(10)\n x$a()\n @export",
+                  name: "MyClass",
+                  methods,
+              });
+      }
+      struct __MyClass {}
+      impl TryFrom<Robj> for &__MyClass {
           type Error = extendr_api::Error;
           fn try_from(robj: Robj) -> extendr_api::Result<Self> {
               Self::try_from(&robj)
           }
       }
-      impl TryFrom<Robj> for &mut MyClass {
+      impl TryFrom<Robj> for &mut __MyClass {
           type Error = extendr_api::Error;
           fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
               Self::try_from(&mut robj)
           }
       }
-      impl TryFrom<&Robj> for &MyClass {
+      impl TryFrom<&Robj> for &__MyClass {
           type Error = extendr_api::Error;
           fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
               use extendr_api::ExternalPtr;
               unsafe {
-                  let external_ptr: &ExternalPtr<MyClass> = robj.try_into()?;
+                  let external_ptr: &ExternalPtr<__MyClass> = robj.try_into()?;
                   external_ptr.try_addr()
               }
           }
       }
-      impl TryFrom<&mut Robj> for &mut MyClass {
+      impl TryFrom<&mut Robj> for &mut __MyClass {
           type Error = extendr_api::Error;
           fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
               use extendr_api::ExternalPtr;
               unsafe {
-                  let external_ptr: &mut ExternalPtr<MyClass> = robj.try_into()?;
+                  let external_ptr: &mut ExternalPtr<__MyClass> = robj.try_into()?;
                   external_ptr.try_addr_mut()
               }
           }
       }
-      impl From<MyClass> for Robj {
-          fn from(value: MyClass) -> Self {
+      impl From<__MyClass> for Robj {
+          fn from(value: __MyClass) -> Self {
               use extendr_api::ExternalPtr;
               unsafe {
-                  let mut res: ExternalPtr<MyClass> = ExternalPtr::new(value);
-                  res.set_attrib(class_symbol(), "MyClass").unwrap();
+                  let mut res: ExternalPtr<__MyClass> = ExternalPtr::new(value);
+                  res.set_attrib(class_symbol(), "__MyClass").unwrap();
                   res.into()
               }
           }
       }
-      #[allow(non_snake_case)]
-      fn meta__MyClass(impls: &mut Vec<extendr_api::metadata::Impl>) {
-          let mut methods = Vec::new();
-          meta__MyClass__new(&mut methods);
-          meta__MyClass__set_a(&mut methods);
-          meta__MyClass__a(&mut methods);
-          meta__MyClass__me(&mut methods);
-          meta__MyClass__restore_from_robj(&mut methods);
-          meta__MyClass__get_default_value(&mut methods);
-          impls
-              .push(extendr_api::metadata::Impl {
-                  doc: " Class for testing (exported)\n @examples\n x <- MyClass$new()\n x$a()\n x$set_a(10)\n x$a()\n @export",
-                  name: "MyClass",
-                  methods,
-              });
-      }
-      struct __MyClass {}
       #[automatically_derived]
       impl ::core::default::Default for __MyClass {
           #[inline]
@@ -10553,63 +11350,63 @@
                   hidden: false,
               })
       }
-      impl TryFrom<Robj> for &__MyClass {
+      #[allow(non_snake_case)]
+      fn meta____MyClass(impls: &mut Vec<extendr_api::metadata::Impl>) {
+          let mut methods = Vec::new();
+          meta____MyClass__new(&mut methods);
+          meta____MyClass____name_test(&mut methods);
+          impls
+              .push(extendr_api::metadata::Impl {
+                  doc: "",
+                  name: "__MyClass",
+                  methods,
+              });
+      }
+      struct MyClassUnexported {
+          a: i32,
+      }
+      impl TryFrom<Robj> for &MyClassUnexported {
           type Error = extendr_api::Error;
           fn try_from(robj: Robj) -> extendr_api::Result<Self> {
               Self::try_from(&robj)
           }
       }
-      impl TryFrom<Robj> for &mut __MyClass {
+      impl TryFrom<Robj> for &mut MyClassUnexported {
           type Error = extendr_api::Error;
           fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
               Self::try_from(&mut robj)
           }
       }
-      impl TryFrom<&Robj> for &__MyClass {
+      impl TryFrom<&Robj> for &MyClassUnexported {
           type Error = extendr_api::Error;
           fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
               use extendr_api::ExternalPtr;
               unsafe {
-                  let external_ptr: &ExternalPtr<__MyClass> = robj.try_into()?;
+                  let external_ptr: &ExternalPtr<MyClassUnexported> = robj.try_into()?;
                   external_ptr.try_addr()
               }
           }
       }
-      impl TryFrom<&mut Robj> for &mut __MyClass {
+      impl TryFrom<&mut Robj> for &mut MyClassUnexported {
           type Error = extendr_api::Error;
           fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
               use extendr_api::ExternalPtr;
               unsafe {
-                  let external_ptr: &mut ExternalPtr<__MyClass> = robj.try_into()?;
+                  let external_ptr: &mut ExternalPtr<MyClassUnexported> = robj.try_into()?;
                   external_ptr.try_addr_mut()
               }
           }
       }
-      impl From<__MyClass> for Robj {
-          fn from(value: __MyClass) -> Self {
+      impl From<MyClassUnexported> for Robj {
+          fn from(value: MyClassUnexported) -> Self {
               use extendr_api::ExternalPtr;
               unsafe {
-                  let mut res: ExternalPtr<__MyClass> = ExternalPtr::new(value);
-                  res.set_attrib(class_symbol(), "__MyClass").unwrap();
+                  let mut res: ExternalPtr<MyClassUnexported> = ExternalPtr::new(value);
+                  res.set_attrib(class_symbol(), "MyClassUnexported").unwrap();
                   res.into()
               }
           }
       }
-      #[allow(non_snake_case)]
-      fn meta____MyClass(impls: &mut Vec<extendr_api::metadata::Impl>) {
-          let mut methods = Vec::new();
-          meta____MyClass__new(&mut methods);
-          meta____MyClass____name_test(&mut methods);
-          impls
-              .push(extendr_api::metadata::Impl {
-                  doc: "",
-                  name: "__MyClass",
-                  methods,
-              });
-      }
-      struct MyClassUnexported {
-          a: i32,
-      }
       #[automatically_derived]
       impl ::core::default::Default for MyClassUnexported {
           #[inline]
@@ -10803,48 +11600,6 @@
                   hidden: false,
               })
       }
-      impl TryFrom<Robj> for &MyClassUnexported {
-          type Error = extendr_api::Error;
-          fn try_from(robj: Robj) -> extendr_api::Result<Self> {
-              Self::try_from(&robj)
-          }
-      }
-      impl TryFrom<Robj> for &mut MyClassUnexported {
-          type Error = extendr_api::Error;
-          fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
-              Self::try_from(&mut robj)
-          }
-      }
-      impl TryFrom<&Robj> for &MyClassUnexported {
-          type Error = extendr_api::Error;
-          fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
-              use extendr_api::ExternalPtr;
-              unsafe {
-                  let external_ptr: &ExternalPtr<MyClassUnexported> = robj.try_into()?;
-                  external_ptr.try_addr()
-              }
-          }
-      }
-      impl TryFrom<&mut Robj> for &mut MyClassUnexported {
-          type Error = extendr_api::Error;
-          fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
-              use extendr_api::ExternalPtr;
-              unsafe {
-                  let external_ptr: &mut ExternalPtr<MyClassUnexported> = robj.try_into()?;
-                  external_ptr.try_addr_mut()
-              }
-          }
-      }
-      impl From<MyClassUnexported> for Robj {
-          fn from(value: MyClassUnexported) -> Self {
-              use extendr_api::ExternalPtr;
-              unsafe {
-                  let mut res: ExternalPtr<MyClassUnexported> = ExternalPtr::new(value);
-                  res.set_attrib(class_symbol(), "MyClassUnexported").unwrap();
-                  res.into()
-              }
-          }
-      }
       #[allow(non_snake_case)]
       fn meta__MyClassUnexported(impls: &mut Vec<extendr_api::metadata::Impl>) {
           let mut methods = Vec::new();
diff --git a/tests/extendrtests/tests/testthat/test-extendr_impl.R b/tests/extendrtests/tests/testthat/test-extendr_impl.R
index baa510406..e8ecc118e 100644
--- a/tests/extendrtests/tests/testthat/test-extendr_impl.R
+++ b/tests/extendrtests/tests/testthat/test-extendr_impl.R
@@ -1,4 +1,4 @@
-test_that("return self reference",  {
+test_that("return self reference", {
   x <- Wrapper$new()
   x$set_a(10)
 
@@ -49,3 +49,17 @@ test_that("return the right reference with other parameters present", {
   expect_identical(max_class, y)
   expect_false(isTRUE(identical(x, max_class)))
 })
+
+test_that("submodule impl for Wrapper exists", {
+  x <- Wrapper$new()
+  x$set_a(10)
+  expect_identical(x$a_10(), 20L)
+})
+
+test_that("enum wrapper for Animal works", {
+  cat <- Animal$new_cat()
+  expect_identical(cat$speak(), "meow")
+  dog <- Animal$new_dog()
+  expect_identical(dog$speak(), "woof")
+  expect_false(identical(cat, dog))
+})
diff --git a/tests/extendrtests/tests/testthat/test-matrix.R b/tests/extendrtests/tests/testthat/test-matrix.R
index 93edf9ba8..3f7228e9f 100644
--- a/tests/extendrtests/tests/testthat/test-matrix.R
+++ b/tests/extendrtests/tests/testthat/test-matrix.R
@@ -23,3 +23,28 @@ test_that("Dimnames can be changed", {
   dimnames(m2) <- list(c("AA", "BB", "CC"), NULL)
   expect_equal(change_dimnames(m), m2)
 })
+
+test_that("Matrix3D with non-square shape is returned as is", {
+  m3d <- array(as.double(1:(2*3*4)), dim = c(2, 3, 4))
+  expect_equal(matrix_3d_return(m3d), m3d)
+})
+
+test_that("Matrix4D with non-square shape is returned as is", {
+  m3d <- array(as.double(1:(2*3*4*5)), dim = c(2, 3, 4, 5))
+  expect_equal(matrix_4d_return(m3d), m3d)
+})
+
+test_that("Matrix4D should error when dimensions are different", {
+  m3d <- array(as.double(1:(2*3*4*5*6)), dim = c(2, 3, 4, 5, 6))
+  expect_error(matrix_4d_return(m3d))
+})
+
+test_that("Matrix5D with non-square shape is returned as is", {
+  m3d <- array(as.double(1:(2*3*4*5*6)), dim = c(2, 3, 4, 5, 6))
+  expect_equal(matrix_5d_return(m3d), m3d)
+})
+
+test_that("Matrix5D should error when dimensions are different", {
+  m3d <- array(as.double(1:(2*3*4*5)), dim = c(2, 3, 4, 5))
+  expect_error(matrix_5d_return(m3d))
+})