fp_bindgen_macros/
lib.rs

1use crate::{primitives::Primitive, utils::extract_path_from_type};
2use proc_macro::{TokenStream, TokenTree};
3use proc_macro_error::{abort, proc_macro_error, ResultExt};
4use quote::{format_ident, quote, ToTokens};
5use std::{
6    collections::{HashMap, HashSet},
7    iter::once,
8};
9use syn::{
10    AttributeArgs, FnArg, ForeignItemFn, GenericParam, ItemFn, ItemType, ItemUse, Pat, PatPath,
11    Path, PathArguments, PathSegment, ReturnType,
12};
13use utils::{flatten_using_statement, normalize_return_type};
14
15mod primitives;
16mod serializable;
17mod typing;
18mod utils;
19
20/// Used to annotate types (`enum`s and `struct`s) that can be passed across the Wasm bridge.
21#[proc_macro_derive(Serializable, attributes(fp))]
22pub fn derive_serializable(item: TokenStream) -> TokenStream {
23    crate::serializable::impl_derive_serializable(item)
24}
25
26/// Declares functions the plugin can import from the host runtime.
27#[proc_macro]
28pub fn fp_import(token_stream: TokenStream) -> TokenStream {
29    let ParsedStatements {
30        functions,
31        collectable_types,
32        aliases,
33    } = parse_statements(token_stream);
34    let collectable_types = collectable_types.iter();
35    let alias_keys = aliases.keys();
36    let alias_paths = aliases
37        .values()
38        .map(|path| path.to_token_stream().to_string());
39
40    let replacement = quote! {
41        fn __fp_declare_import_fns() -> (fp_bindgen::prelude::FunctionList, fp_bindgen::prelude::TypeMap) {
42            let mut import_types = fp_bindgen::prelude::TypeMap::new();
43            #( #collectable_types::collect_types(&mut import_types); )*
44            #( import_types.insert(TypeIdent::from(#alias_keys), Type::Alias(#alias_keys.to_owned(), std::str::FromStr::from_str(#alias_paths).unwrap())); )*
45
46            let mut list = fp_bindgen::prelude::FunctionList::new();
47            #( list.add_function(#functions); )*
48
49            (list, import_types)
50        }
51    };
52    replacement.into()
53}
54
55/// Declares functions the plugin may export to the host runtime.
56#[proc_macro]
57pub fn fp_export(token_stream: TokenStream) -> TokenStream {
58    let ParsedStatements {
59        functions,
60        collectable_types,
61        aliases,
62    } = parse_statements(token_stream);
63    let collectable_types = collectable_types.iter();
64    let alias_keys = aliases.keys();
65    let alias_paths = aliases
66        .values()
67        .map(|path| path.to_token_stream().to_string());
68
69    let replacement = quote! {
70        fn __fp_declare_export_fns() -> (fp_bindgen::prelude::FunctionList, fp_bindgen::prelude::TypeMap) {
71            let mut export_types = fp_bindgen::prelude::TypeMap::new();
72            #( #collectable_types::collect_types(&mut export_types); )*
73            #( export_types.insert(TypeIdent::from(#alias_keys), Type::Alias(#alias_keys.to_owned(), std::str::FromStr::from_str(#alias_paths).unwrap())); )*
74
75            let mut list = fp_bindgen::prelude::FunctionList::new();
76            #( list.add_function(#functions); )*
77
78            (list, export_types)
79        }
80    };
81    replacement.into()
82}
83
84/// Contains all the relevant information extracted from inside the `fp_import!` and `fp_export!`
85/// macros.
86struct ParsedStatements {
87    pub functions: Vec<String>,
88    pub collectable_types: HashSet<CollectableTypeDefinition>,
89    pub aliases: HashMap<String, CollectableTypeDefinition>,
90}
91
92/// A type definition on which we can call ::collect_types()
93#[derive(Debug, Eq, PartialEq, Hash)]
94struct CollectableTypeDefinition {
95    pub path: Path,
96    pub array_len: usize,
97}
98
99impl ToTokens for CollectableTypeDefinition {
100    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
101        let path = &self.path;
102        if self.array_len > 0 {
103            let len = self.array_len;
104            tokens.extend(quote! { <[#path; #len]> })
105        } else {
106            tokens.extend(quote! { #path })
107        }
108    }
109}
110
111/// Parses statements like function declarations and 'use Foobar;' and returns them in a list.
112/// In addition, it returns a list of doc lines for every function as well.
113/// Finally, it returns two sets: one with all the paths for types that may need serialization
114/// to call the functions, and one with all the paths for types that may need deserialization to
115/// call the functions.
116fn parse_statements(token_stream: TokenStream) -> ParsedStatements {
117    let mut functions = Vec::new();
118    let mut collectable_types = HashSet::new();
119    let mut aliases = HashMap::new();
120
121    let mut current_item_tokens = Vec::<TokenTree>::new();
122    for token in token_stream.into_iter() {
123        match token {
124            TokenTree::Punct(punct) if punct.as_char() == ';' => {
125                current_item_tokens.push(TokenTree::Punct(punct));
126
127                let stream = current_item_tokens.into_iter().collect::<TokenStream>();
128
129                if let Ok(function) = syn::parse::<ForeignItemFn>(stream.clone()) {
130                    for input in &function.sig.inputs {
131                        match input {
132                            FnArg::Receiver(_) => panic!(
133                                "Methods are not supported. Found `self` in function declaration: {:?}",
134                                function.sig
135                            ),
136                            FnArg::Typed(arg) => {
137                                collectable_types.insert(
138                                    extract_path_from_type(arg.ty.as_ref()).unwrap_or_else(|| {
139                                        panic!(
140                                            "Only value types are supported. \
141                                                Incompatible argument type in function declaration: {:?}",
142                                            function.sig
143                                        )
144                                    }),
145                                );
146                            }
147                        }
148                    }
149
150                    if let Some(ty) = normalize_return_type(&function.sig.output) {
151                        collectable_types.insert(extract_path_from_type(ty).unwrap_or_else(|| {
152                            panic!(
153                                "Only value types are supported. \
154                                            Incompatible return type in function declaration: {:?}",
155                                function.sig
156                            )
157                        }));
158                    }
159
160                    functions.push(function.into_token_stream().to_string());
161                } else if let Ok(using) = syn::parse::<ItemUse>(stream.clone()) {
162                    for path in flatten_using_statement(using) {
163                        collectable_types.insert(CollectableTypeDefinition { path, array_len: 0 });
164                    }
165                } else if let Ok(type_alias) = syn::parse::<ItemType>(stream) {
166                    aliases.insert(
167                        type_alias.ident.to_string(),
168                        extract_path_from_type(type_alias.ty.as_ref()).unwrap_or_else(|| {
169                            panic!(
170                                "Only value types are supported. \
171                                    Incompatible type in alias: {:?}",
172                                type_alias
173                            )
174                        }),
175                    );
176                }
177
178                current_item_tokens = Vec::new();
179            }
180            other => current_item_tokens.push(other),
181        }
182    }
183
184    ParsedStatements {
185        functions,
186        collectable_types,
187        aliases,
188    }
189}
190
191/// Generates bindings for the functions declared in the `fp_import!{}` and `fp_export!{}` blocks.
192#[proc_macro]
193pub fn fp_bindgen(args: TokenStream) -> TokenStream {
194    let args: proc_macro2::TokenStream = args.into();
195    let replacement = quote! {
196        let (import_functions, import_types) = __fp_declare_import_fns();
197        let (export_functions, mut export_types) = __fp_declare_export_fns();
198
199        let mut types = import_types;
200        types.append(&mut export_types);
201
202        fp_bindgen::generate_bindings(
203            import_functions,
204            export_functions,
205            types,
206            #args
207        );
208    };
209    replacement.into()
210}
211
212#[doc(hidden)]
213#[proc_macro]
214pub fn primitive_impls(_: TokenStream) -> TokenStream {
215    let primitives = [
216        Primitive::Bool,
217        Primitive::F32,
218        Primitive::F64,
219        Primitive::I8,
220        Primitive::I16,
221        Primitive::I32,
222        Primitive::I64,
223        Primitive::U8,
224        Primitive::U16,
225        Primitive::U32,
226        Primitive::U64,
227    ];
228
229    let mut token_stream = TokenStream::new();
230    for primitive in primitives {
231        token_stream.extend(primitive.gen_impl().into_iter());
232    }
233    token_stream
234}
235
236/// Exports a signature in a provider crate.
237/// This is not meant to be used directly.
238#[proc_macro_attribute]
239#[proc_macro_error]
240pub fn fp_export_signature(_attributes: TokenStream, input: TokenStream) -> TokenStream {
241    proc_macro_error::set_dummy(input.clone().into());
242
243    let func = syn::parse_macro_input::parse::<ForeignItemFn>(input.clone()).unwrap_or_abort();
244    let args = typing::extract_args(&func.sig).collect::<Vec<_>>();
245
246    let mut sig = func.sig.clone();
247    //Massage the signature into what we wish to export
248    {
249        typing::morph_signature(&mut sig, "fp_bindgen_support");
250        sig.inputs = sig
251            .inputs
252            .into_iter()
253            //append a function ptr to the end which signature matches the original exported function
254            .chain(once({
255                let input_types = args.iter().map(|(_, pt, _)| pt.ty.as_ref());
256                let output = if func.sig.asyncness.is_some() {
257                    syn::parse::<ReturnType>((quote! {-> FUT}).into()).unwrap_or_abort()
258                } else {
259                    func.sig.output.clone()
260                };
261
262                syn::parse::<FnArg>((quote! {fptr: fn (#(#input_types),*) #output}).into())
263                    .unwrap_or_abort()
264            }))
265            .collect();
266        sig.generics.params.clear();
267        if func.sig.asyncness.is_some() {
268            let output = typing::get_output_type(&func.sig.output);
269            sig.generics.params.push(
270                syn::parse::<GenericParam>(
271                    //the 'static life time is ok since we give it a box::pin
272                    match output {
273                        Some(output) => {
274                            (quote! {FUT: std::future::Future<Output=#output> + 'static}).into()
275                        }
276                        None => (quote! {FUT: std::future::Future<Output=()> + 'static}).into(),
277                    },
278                )
279                .unwrap_or_abort(),
280            )
281        }
282    }
283
284    let (complex_names, complex_types): (Vec<_>, Vec<_>) = args
285        .iter()
286        .filter_map(|&(_, pt, is_complex)| {
287            if is_complex {
288                Some((pt.pat.as_ref(), pt.ty.as_ref()))
289            } else {
290                None
291            }
292        })
293        .unzip();
294
295    let names = args.iter().map(|(_, pt, _)| pt.pat.as_ref());
296    let func_call = quote! {(fptr)(#(#names),*)};
297
298    let func_wrapper = if func.sig.asyncness.is_some() {
299        quote! {
300            let ret = fp_bindgen_support::guest::r#async::task::Task::alloc_and_spawn(#func_call);
301        }
302    } else {
303        // Check the output type and replace complex ones with FatPtr
304        let return_wrapper = if typing::is_ret_type_complex(&func.sig.output) {
305            quote! {let ret = fp_bindgen_support::guest::io::export_value_to_host(&ret);}
306        } else {
307            Default::default()
308        };
309        quote! {
310            let ret = #func_call;
311            #return_wrapper
312        }
313    };
314
315    //build the actual exported wrapper function
316    (quote! {
317        /// This is a implementation detail an should not be called directly
318        #[inline(always)]
319        pub #sig {
320            #(let #complex_names = unsafe { fp_bindgen_support::guest::io::import_value_from_host::<#complex_types>(#complex_names) };)*
321            #func_wrapper
322            ret
323        }
324    })
325    .into()
326}
327
328/// Exports an implementation of a specific provider function
329///
330/// Example usage of implementing a `log` function of a `logger` provider:
331/// ```no_compile
332/// use fp_bindgen_macros::fp_export_impl; //this would be `logger::fp_export_impl` inside the plugin crate
333/// #[fp_export_impl(logger)]
334/// pub fn log(msg: String, foo: String) -> String {
335///     format!("{} + {} => {0}{1}", msg, foo)
336/// }
337/// ```
338#[proc_macro_attribute]
339#[proc_macro_error]
340pub fn fp_export_impl(attributes: TokenStream, input: TokenStream) -> TokenStream {
341    proc_macro_error::set_dummy(input.clone().into());
342
343    let func = syn::parse_macro_input::parse::<ItemFn>(input.clone()).unwrap_or_abort();
344    let attrs =
345        syn::parse_macro_input::parse::<AttributeArgs>(attributes.clone()).unwrap_or_abort();
346
347    let protocol_path = attrs
348        .get(0)
349        .map(|om| match om {
350            syn::NestedMeta::Meta(meta) => match meta {
351                syn::Meta::Path(path) => path,
352                _ => abort!(meta, "unsupported attribute, must name a path"),
353            },
354            _ => abort!(om, "unsupported attribute, must name a path"),
355        })
356        .unwrap_or_else(|| abort!(func, "missing attribute. Must name which provider is being implemented eg: #[fp_export_impl(foobar)]"));
357
358    let args = typing::extract_args(&func.sig).collect::<Vec<_>>();
359
360    let mut sig = func.sig.clone();
361    //Massage the signature into what we wish to export
362    {
363        typing::morph_signature(
364            &mut sig,
365            protocol_path.to_token_stream().to_string().as_str(),
366        );
367        sig.ident = format_ident!("__fp_gen_{}", sig.ident);
368    }
369
370    let fn_name = &func.sig.ident;
371
372    let impl_fn_pat = Pat::Path(PatPath {
373        attrs: vec![],
374        qself: None,
375        path: PathSegment {
376            ident: func.sig.ident.clone(),
377            arguments: PathArguments::None,
378        }
379        .into(),
380    });
381    let call_args = args
382        .iter()
383        .map(|&(_, pt, _)| pt.pat.as_ref())
384        .chain(once(&impl_fn_pat))
385        .collect::<Vec<_>>();
386
387    let ts: proc_macro2::TokenStream = input.clone().into();
388    //build the actual exported wrapper function
389    (quote! {
390        #[no_mangle]
391        pub #sig {
392            #protocol_path::#fn_name(#(#call_args),*)
393        }
394        #ts
395    })
396    .into()
397}
398
399/// Imports a signature in a provider crate.
400/// This is not meant to be used directly.
401#[proc_macro_attribute]
402#[proc_macro_error]
403pub fn fp_import_signature(_attributes: TokenStream, input: TokenStream) -> TokenStream {
404    proc_macro_error::set_dummy(input.clone().into());
405
406    let func = syn::parse_macro_input::parse::<ForeignItemFn>(input.clone()).unwrap_or_abort();
407    let args = typing::extract_args(&func.sig).collect::<Vec<_>>();
408
409    let wrapper_sig = func.sig.clone();
410    let mut extern_sig = wrapper_sig.clone();
411    //Massage the signature into what we wish to export
412    {
413        extern_sig.ident = format_ident!("__fp_gen_{}", extern_sig.ident);
414        typing::morph_signature(&mut extern_sig, "fp_bindgen_support");
415    }
416
417    let complex_names: Vec<_> = args
418        .iter()
419        .filter_map(|&(_, pt, is_complex)| {
420            if is_complex {
421                Some(pt.pat.as_ref())
422            } else {
423                None
424            }
425        })
426        .collect();
427
428    let names = args.iter().map(|(_, pt, _)| pt.pat.as_ref());
429    let extern_ident = &extern_sig.ident;
430    let func_call = quote! {#extern_ident(#(#names),*)};
431
432    let ret_wrapper = if func.sig.asyncness.is_some() {
433        quote! {
434            let ret = unsafe {
435                fp_bindgen_support::guest::io::import_value_from_host(fp_bindgen_support::guest::r#async::HostFuture::new(ret).await)
436            };
437        }
438    } else {
439        // Check the output type and replace complex ones with FatPtr
440        if typing::is_ret_type_complex(&func.sig.output) {
441            quote! {
442                let ret = unsafe { fp_bindgen_support::guest::io::import_value_from_host(ret) };
443            }
444        } else {
445            Default::default()
446        }
447    };
448
449    let attrs = &func.attrs;
450
451    //build the actual imported wrapper function
452    (quote! {
453        #[link(wasm_import_module = "fp")]
454        extern "C" { #extern_sig; }
455
456        #[inline(always)]
457        #(#attrs)*
458        pub #wrapper_sig {
459            #(let #complex_names = fp_bindgen_support::guest::io::export_value_to_host(&#complex_names);)*
460            let ret = unsafe { #func_call };
461            #ret_wrapper
462            ret
463        }
464    })
465    .into()
466}