byond_fn_impl/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4
5use proc_macro2::{Ident, TokenStream as TokenStream2};
6use proc_macro_error::{abort, proc_macro_error};
7use quote::quote;
8use syn::spanned::Spanned;
9use syn::{FnArg, ItemFn, Signature, Type};
10
11#[cfg(feature = "ffi_v2")]
12mod ffi_v2;
13mod str_ffi;
14
15pub(crate) struct FFITokens {
16    fn_args: TokenStream2,
17    return_type: TokenStream2,
18    fn_body: TokenStream2,
19}
20
21fn is_option_type(arg: &FnArg) -> bool {
22    match arg {
23        FnArg::Receiver(_) => abort!(arg.span(), "byond_fn can't have self argument"),
24        FnArg::Typed(arg) => match *arg.ty {
25            Type::Path(ref path) => path.path.segments.last().unwrap().ident == "Option",
26            _ => false,
27        },
28    }
29}
30
31#[proc_macro_error]
32#[proc_macro_attribute]
33pub fn byond_fn(args: TokenStream, input: TokenStream) -> TokenStream {
34    byond_fn2(args.into(), input.into()).into()
35}
36
37const STR_FFI_DESC: &str = "\"str\" (default): FFI with C Strings as the interop type";
38const FFI_V2_DESC: &str =
39    "\"v2\": New FFI Format added with BYOND 515 that uses `ByondType` as the FFI medium";
40
41fn byond_fn2(proc_args: TokenStream2, input: TokenStream2) -> TokenStream2 {
42    let original_fn: ItemFn = syn::parse2(input).unwrap();
43
44    let proc_args: Ident =
45        syn::parse2(proc_args.clone()).unwrap_or(Ident::new("default", proc_args.span()));
46
47    let sig = &original_fn.sig;
48
49    let Signature { ident, inputs, .. } = &sig;
50
51    //verify optional params are at the tail of the sig
52    let mut optional_encountered = false;
53    for arg in inputs.iter() {
54        if optional_encountered && !is_option_type(arg) {
55            abort!(
56                arg.span(),
57                "Optional arguments must be at the end of the function signature"
58            );
59        } else {
60            optional_encountered = is_option_type(arg);
61        }
62    }
63
64    let mangled_name = Ident::new(
65        format!("__byond_fn_{}", ident.to_string()).as_str(),
66        ident.span(),
67    );
68
69    let FFITokens {
70        fn_args,
71        return_type,
72        fn_body,
73    } = str_ffi::tokens(sig);
74
75    quote! {
76        #original_fn
77        mod #mangled_name {
78            #[no_mangle]
79            pub unsafe extern "C" fn #ident(#fn_args) -> #return_type {
80                #fn_body
81            }
82        }
83    }
84}
85
86#[cfg(test)]
87mod test {
88    use quote::quote;
89
90    use super::*;
91
92    #[test]
93    fn is_optional_valid() {
94        let arg: FnArg = syn::parse2(quote! { foo: i32 }).unwrap();
95        assert!(!is_option_type(&arg));
96
97        let arg: FnArg = syn::parse2(quote! { foo: Option<i32> }).unwrap();
98        assert!(is_option_type(&arg));
99    }
100}