trait_ffi/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use convert_case::Casing;
4use proc_macro::TokenStream;
5use proc_macro2::Span;
6use quote::{format_ident, quote};
7use syn::{Ident, ItemImpl, ItemTrait, parse_macro_input, spanned::Spanned};
8
9macro_rules! bail {
10    ($i:expr, $msg:expr) => {
11        return syn::parse::Error::new($i, $msg).to_compile_error().into();
12    };
13}
14
15fn get_crate_name() -> String {
16    std::env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "unknown".to_string())
17}
18
19fn get_crate_version() -> String {
20    std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.1.0".to_string())
21}
22
23fn prefix_version() -> String {
24    let version = lenient_semver::parse(&get_crate_version()).unwrap();
25    let major = version.major;
26    let minor = version.minor;
27    if major == 0 {
28        format!("0_{minor}")
29    } else {
30        major.to_string()
31    }
32}
33
34fn extern_fn_name(crate_name: &str, fn_name: &Ident) -> Ident {
35    let crate_name = crate_name.to_lowercase().replace("-", "_");
36    let version = prefix_version();
37
38    format_ident!("__{crate_name}_{version}_{fn_name}")
39}
40
41fn parse_def_extern_trait_args(args: TokenStream) -> Result<String, String> {
42    if args.is_empty() {
43        return Ok("rust".to_string()); // 默认使用 Rust ABI
44    }
45
46    let args_str = args.to_string();
47    let mut abi = None;
48
49    // 简单解析 abi="value" 形式
50    let parts: Vec<&str> = args_str.split(',').collect();
51
52    for part in parts {
53        let part = part.trim();
54        if part.starts_with("abi") {
55            if let Some(start) = part.find('"') {
56                if let Some(end) = part.rfind('"') {
57                    if start < end {
58                        abi = Some(part[start + 1..end].to_string());
59                    }
60                }
61            }
62        }
63    }
64
65    let abi = abi.unwrap_or_else(|| "rust".to_string());
66
67    if abi != "c" && abi != "rust" {
68        return Err("Invalid abi parameter. Supported values: \"c\", \"rust\"".to_string());
69    }
70
71    Ok(abi)
72}
73
74/// Defines an extern trait that can be called across FFI boundaries.
75///
76/// This macro converts a regular Rust trait into a trait that can be called through FFI.
77/// It generates:
78/// 1. The original trait definition
79/// 2. A module containing wrapper functions that call external implementations
80/// 3. A helper macro `impl_trait!` for implementing the trait
81/// 4. A checker function to ensure the trait is properly implemented
82///
83/// # Arguments
84/// - `abi`: Optional parameter specifying ABI type ("c" or "rust"), defaults to "rust"
85///
86/// # Example
87/// ```rust
88/// #[def_extern_trait(abi = "c")]
89/// trait Calculator {
90///     fn add(&self, a: i32, b: i32) -> i32;
91///     fn multiply(&self, a: i32, b: i32) -> i32;
92/// }
93/// ```
94///
95/// This will generate a `calculator` module containing functions that can call external implementations.
96#[proc_macro_attribute]
97pub fn def_extern_trait(args: TokenStream, input: TokenStream) -> TokenStream {
98    let abi = match parse_def_extern_trait_args(args) {
99        Ok(abi) => abi,
100        Err(error_msg) => {
101            bail!(Span::call_site(), error_msg);
102        }
103    };
104
105    let input = parse_macro_input!(input as ItemTrait);
106    let vis = input.vis.clone();
107    let mod_name = format_ident!(
108        "{}",
109        input.ident.to_string().to_case(convert_case::Case::Snake)
110    );
111    let crate_name_str = get_crate_name();
112
113    let mut fn_list = vec![];
114
115    for item in &input.items {
116        if let syn::TraitItem::Fn(func) = item {
117            let fn_name = func.sig.ident.clone();
118            let extern_fn_name = extern_fn_name(&crate_name_str, &fn_name);
119
120            let attrs = &func.attrs;
121            let inputs = &func.sig.inputs;
122            let output = &func.sig.output;
123
124            let mut param_names = vec![];
125            let mut param_types = vec![];
126
127            for input in inputs {
128                if let syn::FnArg::Typed(pat_type) = input {
129                    param_names.push(&pat_type.pat);
130                    param_types.push(&pat_type.ty);
131                }
132            }
133
134            let extern_abi = if abi == "rust" { "Rust" } else { "C" };
135
136            fn_list.push(quote! {
137                #(#attrs)*
138                pub fn #fn_name(#inputs) #output {
139                    unsafe extern #extern_abi {
140                        fn #extern_fn_name(#inputs) #output;
141                    }
142                    unsafe{ #extern_fn_name(#(#param_names),*) }
143                }
144            });
145        } else {
146            bail!(
147                item.span(),
148                "Only function items are allowed in extern traits"
149            );
150        }
151    }
152
153    let crate_name = format_ident!("{}", crate_name_str.replace("-", "_"));
154
155    let warn_fn_name = format_ident!(
156        "Trait_{}_in_crate_{}_{}_need_impl",
157        input.ident,
158        crate_name_str.replace("-", "_"),
159        prefix_version()
160    );
161
162    let generated_macro = quote! {
163        #[macro_export]
164        macro_rules! impl_trait {
165            (impl $trait:ident for $type:ty { $($body:tt)* }) => {
166                #[#crate_name::impl_extern_trait(name = #crate_name_str, abi = #abi)]
167                impl $trait for $type {
168                    $($body)*
169                }
170
171                #[allow(snake_case)]
172                #[unsafe(no_mangle)]
173                extern "C" fn #warn_fn_name() { }
174            };
175        }
176    };
177
178    quote! {
179        pub use trait_ffi::impl_extern_trait;
180
181        #input
182
183        #vis mod #mod_name {
184            use super::*;
185            pub fn ____checker_do_not_use(){
186                unsafe extern "C" {
187                    fn #warn_fn_name();
188                }
189                unsafe { #warn_fn_name() };
190            }
191            #(#fn_list)*
192        }
193
194        #generated_macro
195    }
196    .into()
197}
198
199fn parse_extern_trait_args(args: TokenStream) -> Result<(String, String), String> {
200    if args.is_empty() {
201        return Err(
202            "Missing parameters. Usage: #[impl_extern_trait(name=\"crate_name\", abi=\"c\")]"
203                .to_string(),
204        );
205    }
206
207    let args_str = args.to_string();
208    let mut name = None;
209    let mut abi = None;
210
211    let parts: Vec<&str> = args_str.split(',').collect();
212
213    for part in parts {
214        let part = part.trim();
215        if part.starts_with("name") {
216            if let Some(start) = part.find('"') {
217                if let Some(end) = part.rfind('"') {
218                    if start < end {
219                        name = Some(part[start + 1..end].to_string());
220                    }
221                }
222            }
223        } else if part.starts_with("abi") {
224            if let Some(start) = part.find('"') {
225                if let Some(end) = part.rfind('"') {
226                    if start < end {
227                        abi = Some(part[start + 1..end].to_string());
228                    }
229                }
230            }
231        }
232    }
233
234    let name = name.ok_or_else(|| {
235        "Missing name parameter. Usage: #[impl_extern_trait(name=\"crate_name\", abi=\"c\")]"
236            .to_string()
237    })?;
238    let abi = abi.unwrap_or_else(|| "c".to_string());
239
240    if abi != "c" && abi != "rust" {
241        return Err("Invalid abi parameter. Supported values: \"c\", \"rust\"".to_string());
242    }
243
244    Ok((name, abi))
245}
246
247/// Implements an extern trait for a type and generates corresponding C function exports.
248///
249/// This macro takes a trait implementation and generates extern "C" functions that can be
250/// called from other languages. Each method in the trait implementation gets a corresponding
251/// extern function with a mangled name based on the crate name and version.
252///
253/// # Arguments
254/// - `name`: The name of the crate that defines the extern trait
255/// - `abi`: The ABI to use for the extern functions ("c" or "rust"), defaults to "c"
256///
257/// # Example
258/// ```rust
259/// struct Calculator;
260///
261/// #[impl_extern_trait(name = "calculator_crate", abi = "c")]
262/// impl MyTrait for Calculator {
263///     fn add(&self, a: i32, b: i32) -> i32 {
264///         a + b
265///     }
266/// }
267/// ```
268///
269/// This will generate extern "C" functions that can be called from other languages.
270#[proc_macro_attribute]
271pub fn impl_extern_trait(args: TokenStream, input: TokenStream) -> TokenStream {
272    let (crate_name_str, abi) = match parse_extern_trait_args(args) {
273        Ok((name, abi)) => (name, abi),
274        Err(error_msg) => {
275            bail!(Span::call_site(), error_msg);
276        }
277    };
278    let input = parse_macro_input!(input as ItemImpl);
279    let mut extern_fn_list = vec![];
280
281    let struct_name = input.self_ty.clone();
282    let trait_name = input.clone().trait_.unwrap().1;
283
284    for item in &input.items {
285        if let syn::ImplItem::Fn(func) = item {
286            let fn_name_raw = &func.sig.ident;
287            let fn_name = extern_fn_name(&crate_name_str, fn_name_raw);
288
289            let inputs = &func.sig.inputs;
290            let output = &func.sig.output;
291
292            let extern_abi = if abi == "rust" { "Rust" } else { "C" };
293
294            let mut param_names = vec![];
295            let mut param_types = vec![];
296
297            for input in inputs {
298                if let syn::FnArg::Typed(pat_type) = input {
299                    param_names.push(&pat_type.pat);
300                    param_types.push(&pat_type.ty);
301                }
302            }
303
304            extern_fn_list.push(quote! {
305                #[unsafe(no_mangle)]
306                pub extern #extern_abi fn #fn_name(#inputs) #output {
307                    <#struct_name as #trait_name>::#fn_name_raw(#(#param_names),*)
308                }
309            });
310        }
311    }
312
313    quote! {
314        #input
315        #(#extern_fn_list)*
316    }
317    .into()
318}