savvy_bindgen/ir/
savvy_impl.rs

1use syn::parse_quote;
2
3use super::savvy_fn::{SavvyFn, SavvyFnType};
4use crate::utils::extract_docs;
5
6pub struct SavvyImpl {
7    /// Doc comments
8    pub docs: Vec<String>,
9    /// Attributes except for `#[savvy]`
10    pub attrs: Vec<syn::Attribute>,
11    /// Original type name
12    pub ty: syn::Ident,
13    /// Methods and accociated functions
14    pub fns: Vec<SavvyFn>,
15}
16
17impl SavvyImpl {
18    pub fn new(orig: &syn::ItemImpl) -> syn::Result<Self> {
19        let mut attrs = orig.attrs.clone();
20        // Remove #[savvy]
21        attrs.retain(|attr| attr != &parse_quote!(#[savvy]));
22        // Extract doc comments
23        let docs = extract_docs(attrs.as_slice());
24        let self_ty = orig.self_ty.as_ref();
25
26        let ty = match self_ty {
27            syn::Type::Path(type_path) => type_path.path.segments.last().unwrap().ident.clone(),
28            _ => {
29                return Err(syn::Error::new_spanned(self_ty, "Unexpected type"));
30            }
31        };
32
33        let fns = orig
34            .items
35            .clone()
36            .iter()
37            .filter_map(|f| match f {
38                syn::ImplItem::Fn(impl_item_fn) => {
39                    let ty = self_ty.clone();
40                    let fn_type = match impl_item_fn.sig.inputs.first() {
41                        Some(syn::FnArg::Receiver(syn::Receiver {
42                            reference,
43                            mutability,
44                            ..
45                        })) => SavvyFnType::Method {
46                            ty,
47                            reference: reference.is_some(),
48                            mutability: mutability.is_some(),
49                        },
50                        _ => SavvyFnType::AssociatedFunction(ty),
51                    };
52
53                    Some(SavvyFn::from_impl_fn(impl_item_fn, fn_type, self_ty))
54                }
55                _ => None,
56            })
57            .collect::<syn::Result<Vec<SavvyFn>>>()?;
58
59        Ok(Self {
60            docs,
61            attrs,
62            ty,
63            fns,
64        })
65    }
66
67    #[allow(dead_code)]
68    pub fn generate_inner_fns(&self) -> Vec<syn::ItemFn> {
69        self.fns.iter().map(|f| f.generate_inner_fn()).collect()
70    }
71
72    #[allow(dead_code)]
73    pub fn generate_ffi_fns(&self) -> Vec<syn::ItemFn> {
74        self.fns.iter().map(|f| f.generate_ffi_fn()).collect()
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::SavvyFnType::*;
81    use super::*;
82    use syn::parse_quote;
83
84    #[test]
85    fn test_impl() {
86        let item_impl: syn::ItemImpl = parse_quote!(
87            #[savvy]
88            impl Person {
89                fn new() -> Self {
90                    Self {
91                        name: "".to_string(),
92                    }
93                }
94
95                fn set_name(&mut self, name: StringSexp) -> savvy::Result<()> {
96                    self.name = name.iter().next().unwrap().to_string();
97                    Ok(())
98                }
99
100                fn name(&self) -> savvy::Result<savvy::Sexp> {
101                    let mut out = OwnedStringSexp::new(1);
102                    out.set_elt(0, self.name.as_str());
103                    Ok(out.into())
104                }
105
106                fn do_nothing() -> savvy::Result<()> {}
107            }
108        );
109
110        let parsed = SavvyImpl::new(&item_impl).expect("Failed to parse");
111        assert_eq!(parsed.ty.to_string().as_str(), "Person");
112
113        assert_eq!(parsed.fns.len(), 4);
114
115        assert_eq!(parsed.fns[0].fn_name.to_string().as_str(), "new");
116        assert!(matches!(parsed.fns[0].fn_type, AssociatedFunction(_)));
117
118        assert_eq!(parsed.fns[1].fn_name.to_string().as_str(), "set_name");
119        assert!(matches!(parsed.fns[1].fn_type, Method { .. }));
120
121        assert_eq!(parsed.fns[2].fn_name.to_string().as_str(), "name");
122        assert!(matches!(parsed.fns[2].fn_type, Method { .. }));
123
124        assert_eq!(parsed.fns[3].fn_name.to_string().as_str(), "do_nothing");
125        assert!(matches!(parsed.fns[3].fn_type, AssociatedFunction(_)));
126    }
127}