Skip to main content

savvy_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3
4use savvy_bindgen::{SavvyEnum, SavvyFn, SavvyImpl, SavvyStruct};
5use syn::parse_quote;
6
7#[proc_macro_attribute]
8pub fn savvy(_args: TokenStream, input: TokenStream) -> TokenStream {
9    let result = if let Ok(mut item_fn) = syn::parse::<syn::ItemFn>(input.clone()) {
10        savvy_fn(&mut item_fn, false)
11    } else if let Ok(mut item_struct) = syn::parse::<syn::ItemStruct>(input.clone()) {
12        savvy_struct(&mut item_struct)
13    } else if let Ok(mut item_impl) = syn::parse::<syn::ItemImpl>(input.clone()) {
14        savvy_impl(&mut item_impl)
15    } else if let Ok(mut item_enum) = syn::parse::<syn::ItemEnum>(input.clone()) {
16        savvy_enum(&mut item_enum)
17    } else {
18        // TODO: how can I convert TokenStream to Span?
19        let parse_result = syn::parse::<syn::ItemImpl>(input.clone());
20        return proc_macro::TokenStream::from(
21            syn::Error::new(
22                parse_result.unwrap_err().span(),
23                "#[savvy] macro only accepts `fn`, `struct`, or `impl`",
24            )
25            .into_compile_error(),
26        );
27    };
28
29    match result {
30        Ok(token_stream) => token_stream,
31        Err(e) => e.into_compile_error().into(),
32    }
33}
34
35#[proc_macro_attribute]
36pub fn savvy_init(_args: TokenStream, input: TokenStream) -> TokenStream {
37    let result = if let Ok(mut item_fn) = syn::parse::<syn::ItemFn>(input.clone()) {
38        savvy_fn(&mut item_fn, true)
39    } else {
40        // TODO: how can I convert TokenStream to Span?
41        let parse_result = syn::parse::<syn::ItemImpl>(input.clone());
42        return proc_macro::TokenStream::from(
43            syn::Error::new(
44                parse_result.unwrap_err().span(),
45                "#[savvy_init] macro only accepts `fn`",
46            )
47            .into_compile_error(),
48        );
49    };
50
51    match result {
52        Ok(token_stream) => token_stream,
53        Err(e) => e.into_compile_error().into(),
54    }
55}
56
57fn savvy_fn(orig: &mut syn::ItemFn, as_init_fn: bool) -> syn::Result<TokenStream> {
58    let savvy_fn = SavvyFn::from_fn(orig, as_init_fn)?;
59
60    let item_fn_inner = savvy_fn.generate_inner_fn();
61    let item_fn_ffi = savvy_fn.generate_ffi_fn();
62
63    // Make public in order to make it easy to test
64    orig.vis = syn::Visibility::Public(parse_quote!(pub));
65
66    Ok(quote! {
67        #orig
68
69        #item_fn_inner
70        #item_fn_ffi
71    }
72    .into())
73}
74
75fn savvy_impl(orig: &mut syn::ItemImpl) -> syn::Result<TokenStream> {
76    let savvy_impl = SavvyImpl::new(orig)?;
77
78    let list_fn_inner = savvy_impl.generate_inner_fns();
79    let list_fn_ffi = savvy_impl.generate_ffi_fns();
80
81    // Make public in order to make it easy to test
82    for i in orig.items.iter_mut() {
83        match i {
84            syn::ImplItem::Const(c) => {
85                c.vis = syn::Visibility::Public(parse_quote!(pub));
86            }
87            syn::ImplItem::Fn(f) => {
88                f.vis = syn::Visibility::Public(parse_quote!(pub));
89            }
90            syn::ImplItem::Type(t) => {
91                t.vis = syn::Visibility::Public(parse_quote!(pub));
92            }
93            _ => {}
94        }
95    }
96
97    Ok(quote! {
98        #orig
99
100        #(#list_fn_inner)*
101        #(#list_fn_ffi)*
102    }
103    .into())
104}
105
106fn savvy_struct(orig: &mut syn::ItemStruct) -> syn::Result<TokenStream> {
107    let savvy_struct = SavvyStruct::new(orig)?;
108    let try_from_impls = savvy_struct.generate_try_from_impls();
109
110    // Make public in order to make it easy to test
111    orig.vis = syn::Visibility::Public(parse_quote!(pub));
112
113    Ok(quote!(
114        #orig
115
116        #(#try_from_impls)*
117    )
118    .into())
119}
120
121fn savvy_enum(orig: &mut syn::ItemEnum) -> syn::Result<TokenStream> {
122    let savvy_enum = SavvyEnum::new(orig)?;
123    let enum_with_discriminant = savvy_enum.generate_enum_with_discriminant();
124    let try_from_impls = savvy_enum.generate_try_from_impls();
125
126    // Make public in order to make it easy to test
127    orig.vis = syn::Visibility::Public(parse_quote!(pub));
128
129    Ok(quote!(
130        #enum_with_discriminant
131
132        #(#try_from_impls)*
133    )
134    .into())
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use prettyplease::unparse;
141    use syn::parse_quote;
142
143    fn assert_snapshot_inner(orig: syn::ItemFn) {
144        let result = SavvyFn::from_fn(&orig, false)
145            .expect("Failed to parse a function")
146            .generate_inner_fn();
147        let formatted = unparse(&parse_quote!(#result));
148        let lines = formatted.lines().collect::<Vec<&str>>();
149        insta::assert_yaml_snapshot!(lines);
150    }
151
152    #[test]
153    fn test_generate_inner_fn() {
154        assert_snapshot_inner(parse_quote!(
155            #[savvy]
156            fn foo() -> savvy::Result<savvy::Sexp> {
157                bar()
158            }
159        ));
160
161        assert_snapshot_inner(parse_quote!(
162            #[savvy]
163            fn foo() -> savvy::Result<()> {
164                bar();
165                Ok(())
166            }
167        ));
168
169        assert_snapshot_inner(
170            // The qualified form (with `savvy::`) and non-qualified form is
171            // kept between conversions.
172            parse_quote!(
173                #[savvy]
174                fn foo(x: RealSexp, y: savvy::IntegerSexp) -> savvy::Result<savvy::Sexp> {
175                    bar()
176                }
177            ),
178        );
179
180        assert_snapshot_inner(parse_quote!(
181            #[savvy]
182            fn foo(x: f64) -> savvy::Result<savvy::Sexp> {
183                bar()
184            }
185        ));
186    }
187
188    fn assert_snapshot_ffi(orig: syn::ItemFn) {
189        let result = SavvyFn::from_fn(&orig, false)
190            .expect("Failed to parse an impl")
191            .generate_ffi_fn();
192        let formatted = unparse(&parse_quote!(#result));
193        let lines = formatted.lines().collect::<Vec<&str>>();
194        insta::assert_yaml_snapshot!(lines);
195    }
196
197    #[test]
198    fn test_generate_ffi_fn() {
199        assert_snapshot_ffi(parse_quote!(
200            #[savvy]
201            fn foo() -> savvy::Result<savvy::Sexp> {
202                bar()
203            }
204        ));
205
206        assert_snapshot_ffi(parse_quote!(
207            #[savvy]
208            fn foo() -> savvy::Result<()> {
209                bar();
210                Ok(())
211            }
212        ));
213
214        assert_snapshot_ffi(parse_quote!(
215            #[savvy]
216            fn foo(x: RealSexp, y: savvy::RealSexp) -> savvy::Result<savvy::Sexp> {
217                bar()
218            }
219        ));
220    }
221
222    fn assert_snapshot_ffi_impl(orig: &syn::ItemImpl) {
223        for item_fn in SavvyImpl::new(orig).expect("Failed to parse an impl").fns {
224            let result = item_fn.generate_ffi_fn();
225            let formatted = unparse(&parse_quote!(#result));
226            let lines = formatted.lines().collect::<Vec<&str>>();
227            insta::assert_yaml_snapshot!(lines);
228        }
229    }
230
231    #[test]
232    fn test_generate_ffi_fn_impl() {
233        assert_snapshot_ffi_impl(&parse_quote!(
234            #[savvy]
235            impl Person {
236                fn new() -> Self {
237                    Self {}
238                }
239                fn new2() -> Person {
240                    Person {}
241                }
242                fn name(&self) -> savvy::Result<savvy::Sexp> {
243                    Ok(out.into())
244                }
245                fn set_name(&self, name: StringSexp) -> Result<()> {
246                    Ok(())
247                }
248            }
249        ));
250    }
251}