savvy_bindgen/ir/
savvy_impl.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use syn::parse_quote;

use super::savvy_fn::{SavvyFn, SavvyFnType};
use crate::utils::extract_docs;

pub struct SavvyImpl {
    /// Doc comments
    pub docs: Vec<String>,
    /// Attributes except for `#[savvy]`
    pub attrs: Vec<syn::Attribute>,
    /// Original type name
    pub ty: syn::Ident,
    /// Methods and accociated functions
    pub fns: Vec<SavvyFn>,
}

impl SavvyImpl {
    pub fn new(orig: &syn::ItemImpl) -> syn::Result<Self> {
        let mut attrs = orig.attrs.clone();
        // Remove #[savvy]
        attrs.retain(|attr| attr != &parse_quote!(#[savvy]));
        // Extract doc comments
        let docs = extract_docs(attrs.as_slice());
        let self_ty = orig.self_ty.as_ref();

        let ty = match self_ty {
            syn::Type::Path(type_path) => type_path.path.segments.last().unwrap().ident.clone(),
            _ => {
                return Err(syn::Error::new_spanned(self_ty, "Unexpected type"));
            }
        };

        let fns = orig
            .items
            .clone()
            .iter()
            .filter_map(|f| match f {
                syn::ImplItem::Fn(impl_item_fn) => {
                    let ty = self_ty.clone();
                    let fn_type = match impl_item_fn.sig.inputs.first() {
                        Some(syn::FnArg::Receiver(syn::Receiver {
                            reference,
                            mutability,
                            ..
                        })) => SavvyFnType::Method {
                            ty,
                            reference: reference.is_some(),
                            mutability: mutability.is_some(),
                        },
                        _ => SavvyFnType::AssociatedFunction(ty),
                    };

                    Some(SavvyFn::from_impl_fn(impl_item_fn, fn_type, self_ty))
                }
                _ => None,
            })
            .collect::<syn::Result<Vec<SavvyFn>>>()?;

        Ok(Self {
            docs,
            attrs,
            ty,
            fns,
        })
    }

    #[allow(dead_code)]
    pub fn generate_inner_fns(&self) -> Vec<syn::ItemFn> {
        self.fns.iter().map(|f| f.generate_inner_fn()).collect()
    }

    #[allow(dead_code)]
    pub fn generate_ffi_fns(&self) -> Vec<syn::ItemFn> {
        self.fns.iter().map(|f| f.generate_ffi_fn()).collect()
    }
}

#[cfg(test)]
mod tests {
    use super::SavvyFnType::*;
    use super::*;
    use syn::parse_quote;

    #[test]
    fn test_impl() {
        let item_impl: syn::ItemImpl = parse_quote!(
            #[savvy]
            impl Person {
                fn new() -> Self {
                    Self {
                        name: "".to_string(),
                    }
                }

                fn set_name(&mut self, name: StringSexp) -> savvy::Result<()> {
                    self.name = name.iter().next().unwrap().to_string();
                    Ok(())
                }

                fn name(&self) -> savvy::Result<savvy::Sexp> {
                    let mut out = OwnedStringSexp::new(1);
                    out.set_elt(0, self.name.as_str());
                    Ok(out.into())
                }

                fn do_nothing() -> savvy::Result<()> {}
            }
        );

        let parsed = SavvyImpl::new(&item_impl).expect("Failed to parse");
        assert_eq!(parsed.ty.to_string().as_str(), "Person");

        assert_eq!(parsed.fns.len(), 4);

        assert_eq!(parsed.fns[0].fn_name.to_string().as_str(), "new");
        assert!(matches!(parsed.fns[0].fn_type, AssociatedFunction(_)));

        assert_eq!(parsed.fns[1].fn_name.to_string().as_str(), "set_name");
        assert!(matches!(parsed.fns[1].fn_type, Method { .. }));

        assert_eq!(parsed.fns[2].fn_name.to_string().as_str(), "name");
        assert!(matches!(parsed.fns[2].fn_type, Method { .. }));

        assert_eq!(parsed.fns[3].fn_name.to_string().as_str(), "do_nothing");
        assert!(matches!(parsed.fns[3].fn_type, AssociatedFunction(_)));
    }
}