extendr_macros/
extendr_conversion.rs

1use crate::{extendr_options::ExtendrOptions, wrappers};
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::Attribute;
5use syn::Generics;
6use syn::Ident;
7use syn::Item;
8
9struct TypeFields {
10    ident: Ident,
11    generics: Generics,
12    attrs: Vec<Attribute>,
13}
14
15fn pull_fields(item: &Item) -> syn::Result<TypeFields> {
16    let fields = match item {
17        Item::Struct(str) => TypeFields {
18            ident: str.ident.clone(),
19            generics: str.generics.clone(),
20            attrs: str.attrs.clone(),
21        },
22        Item::Enum(str) => TypeFields {
23            ident: str.ident.clone(),
24            generics: str.generics.clone(),
25            attrs: str.attrs.clone(),
26        },
27        _ => {
28            return Err(syn::Error::new_spanned(
29                item,
30                "#[extendr] conversions can only be built for `struct` and `enum`",
31            ))
32        }
33    };
34    Ok(fields)
35}
36
37pub(crate) fn extendr_type_conversion(item: Item, opts: &ExtendrOptions) -> TokenStream {
38    match do_extendr_type_conversion(item, opts) {
39        Ok(result) => result,
40        Err(e) => e.to_compile_error().into(),
41    }
42}
43
44fn do_extendr_type_conversion(item: Item, _opts: &ExtendrOptions) -> syn::Result<TokenStream> {
45    let TypeFields {
46        ident: self_ty,
47        generics,
48        attrs,
49    } = pull_fields(&item)?;
50    if generics.const_params().count() != 0 {
51        return Err(syn::Error::new_spanned(
52            item,
53            "const params not allowed in #[extendr] impl",
54        ));
55    }
56    let mut self_ty_name = self_ty.to_string();
57    for gen in generics.type_params() {
58        self_ty_name.push('_');
59        self_ty_name.push_str(gen.ident.to_string().as_str());
60    }
61
62    // TODO: Should documenting the struct be moved to R?
63    // At the moment, only documentattion above the impl
64    // block makes it to R.
65    let _doc_string = wrappers::get_doc_string(&attrs);
66
67    let conversion_impls = quote! {
68        // Output conversion function for this type.
69
70        impl TryFrom<Robj> for &#self_ty {
71            type Error = extendr_api::Error;
72
73            fn try_from(robj: Robj) -> extendr_api::Result<Self> {
74                Self::try_from(&robj)
75            }
76        }
77
78        impl TryFrom<Robj> for &mut #self_ty {
79            type Error = extendr_api::Error;
80
81            fn try_from(mut robj: Robj) -> extendr_api::Result<Self> {
82                Self::try_from(&mut robj)
83            }
84        }
85
86        // Output conversion function for this type.
87        impl TryFrom<&Robj> for &#self_ty {
88            type Error = extendr_api::Error;
89            fn try_from(robj: &Robj) -> extendr_api::Result<Self> {
90                use extendr_api::ExternalPtr;
91                unsafe {
92                    let external_ptr: &ExternalPtr<#self_ty> = robj.try_into()?;
93                    external_ptr.try_addr()
94                }
95            }
96        }
97
98        // Input conversion function for a mutable reference to this type.
99        impl TryFrom<&mut Robj> for &mut #self_ty {
100            type Error = extendr_api::Error;
101            fn try_from(robj: &mut Robj) -> extendr_api::Result<Self> {
102                use extendr_api::ExternalPtr;
103                unsafe {
104                    let external_ptr: &mut ExternalPtr<#self_ty> = robj.try_into()?;
105                    external_ptr.try_addr_mut()
106                }
107            }
108        }
109    };
110
111    let output = TokenStream::from(quote! {
112        #item
113
114        #conversion_impls
115
116        // Output conversion function for this type.
117        impl From<#self_ty> for Robj {
118            fn from(value: #self_ty) -> Self {
119                use extendr_api::ExternalPtr;
120                unsafe {
121                    let mut res: ExternalPtr<#self_ty> = ExternalPtr::new(value);
122                    res.set_attrib(class_symbol(), #self_ty_name).unwrap();
123                    res.into()
124                }
125            }
126        }
127
128    });
129    Ok(output)
130}