abi_stable_derive/
sabi_extern_fn_impl.rs

1//! Implementation details of the `#[sabi_extern_fn]` attribute.
2
3use std::mem;
4
5use as_derive_utils::return_spanned_err;
6
7use proc_macro::TokenStream as TokenStream1;
8use proc_macro2::{Span, TokenStream as TokenStream2, TokenTree};
9
10use quote::{quote, ToTokens};
11
12use syn::{Expr, ItemFn};
13
14use crate::parse_or_compile_err;
15
16#[doc(hidden)]
17pub fn sabi_extern_fn(attr: TokenStream1, item: TokenStream1) -> TokenStream1 {
18    parse_or_compile_err(item, move |item| sabi_extern_fn_inner(attr.into(), item)).into()
19}
20
21#[cfg(test)]
22pub(crate) fn sabi_extern_fn_str(attr: &str, item: &str) -> Result<TokenStream2, syn::Error> {
23    syn::parse_str(item).and_then(move |item| {
24        let attr = syn::parse_str::<TokenStream2>(attr)?;
25        sabi_extern_fn_inner(attr, item)
26    })
27}
28
29/// Whether the function contains an early return or not.
30#[derive(Debug, Copy, Clone, PartialEq)]
31pub enum WithEarlyReturn {
32    No,
33    Yes,
34}
35
36/// Converts a function into an `extern "C" fn` which aborts on panic.
37pub(crate) fn convert_to_sabi_extern_fn(with_early_return: WithEarlyReturn, item: &mut ItemFn) {
38    let no_early_return = match with_early_return {
39        WithEarlyReturn::No => Some(quote!( no_early_return; )),
40        WithEarlyReturn::Yes => None,
41    };
42
43    item.sig.abi = Some(syn::Abi {
44        extern_token: Default::default(),
45        name: Some(syn::LitStr::new("C", Span::call_site())),
46    });
47
48    let statements = mem::take(&mut item.block.stmts);
49
50    let x = quote! {
51        ::abi_stable::extern_fn_panic_handling!(
52            #no_early_return
53
54            #(#statements)*
55        )
56    };
57    let x = Expr::Verbatim(x);
58    let x = syn::Stmt::Expr(x);
59    let x = vec![x];
60    item.block.stmts = x;
61}
62
63fn sabi_extern_fn_inner(attr: TokenStream2, mut item: ItemFn) -> Result<TokenStream2, syn::Error> {
64    let with_early_return = match attr.into_iter().next() {
65        Some(TokenTree::Ident(ref ident)) if ident == "no_early_return" => WithEarlyReturn::No,
66        Some(tt) => return_spanned_err!(tt, "Unrecognized `#[sabi_extern_fn]` parameter",),
67        None => WithEarlyReturn::Yes,
68    };
69
70    convert_to_sabi_extern_fn(with_early_return, &mut item);
71
72    Ok(item.into_token_stream())
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn test_output() {
81        let list = vec![
82            (
83                "",
84                r##"
85                    pub fn hello()->RString{
86                        println!("{}",HELLO);
87                        println!(",");
88                        println!("{}",WORLD);
89                    }
90                "##,
91                quote!(
92                    pub extern "C" fn hello() -> RString {
93                        ::abi_stable::extern_fn_panic_handling!(
94                            println!("{}",HELLO);
95                            println!(",");
96                            println!("{}",WORLD);
97                        )
98                    }
99                ),
100            ),
101            (
102                "no_early_return",
103                r##"
104                    pub(crate) extern "Rust" fn hello()->RStr<'_>{
105                        println!("{}",HELLO);
106                        println!(",");
107                        println!("{}",WORLD);
108                        "stuff".into()
109                    }
110                "##,
111                quote!(
112                    pub(crate) extern "C" fn hello() -> RStr<'_> {
113                        ::abi_stable::extern_fn_panic_handling!(
114                            no_early_return;
115                            println!("{}",HELLO);
116                            println!(",");
117                            println!("{}",WORLD);
118                            "stuff".into()
119                        )
120                    }
121                ),
122            ),
123        ];
124
125        for (attr, item, expected) in list {
126            assert_eq!(
127                sabi_extern_fn_str(attr, item).unwrap().to_string(),
128                expected.to_string()
129            );
130        }
131    }
132}