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 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 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 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 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 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 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 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}