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}_{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    let crate_name = format_ident!("{}", crate_name_str.replace("-", "_"));
115
116    let crate_name_version = format!("{}_{}", crate_name_str, prefix_version());
117
118    for item in &input.items {
119        if let syn::TraitItem::Fn(func) = item {
120            let fn_name = func.sig.ident.clone();
121            let extern_fn_name = extern_fn_name(&crate_name_version, &fn_name);
122
123            let attrs = &func.attrs;
124            let inputs = &func.sig.inputs;
125            let output = &func.sig.output;
126            let generics = &func.sig.generics;
127
128            let mut param_names = vec![];
129            let mut param_types = vec![];
130
131            for input in inputs {
132                if let syn::FnArg::Typed(pat_type) = input {
133                    param_names.push(&pat_type.pat);
134                    param_types.push(&pat_type.ty);
135                }
136            }
137
138            let extern_abi = if abi == "rust" { "Rust" } else { "C" };
139
140            fn_list.push(quote! {
141                #(#attrs)*
142                pub fn #fn_name #generics (#inputs) #output {
143                    unsafe extern #extern_abi {
144                        fn #extern_fn_name #generics (#inputs) #output;
145                    }
146                    unsafe{ #extern_fn_name(#(#param_names),*) }
147                }
148            });
149        } else {
150            bail!(
151                item.span(),
152                "Only function items are allowed in extern traits"
153            );
154        }
155    }
156
157    let warn_fn_name = format_ident!(
158        "Trait_{}_in_crate_{}_{}_need_impl",
159        input.ident,
160        crate_name_str.replace("-", "_"),
161        prefix_version()
162    );
163
164
165    let generated_macro = quote! {
166        #[macro_export]
167        macro_rules! impl_trait {
168            (impl $trait:ident for $type:ty { $($body:tt)* }) => {
169                #[#crate_name::impl_extern_trait(name = #crate_name_version, abi = #abi)]
170                impl $trait for $type {
171                    $($body)*
172                }
173
174                #[allow(snake_case)]
175                #[unsafe(no_mangle)]
176                extern "C" fn #warn_fn_name() { }
177            };
178        }
179    };
180
181    quote! {
182        pub use trait_ffi::impl_extern_trait;
183
184        #input
185
186        #vis mod #mod_name {
187            use super::*;
188            /// `trait-ffi` generated.
189            pub fn ____checker_do_not_use(){
190                unsafe extern "C" {
191                    fn #warn_fn_name();
192                }
193                unsafe { #warn_fn_name() };
194            }
195            #(#fn_list)*
196        }
197
198        #generated_macro
199    }
200    .into()
201}
202
203fn parse_extern_trait_args(args: TokenStream) -> Result<(String, String), String> {
204    if args.is_empty() {
205        return Err(
206            "Missing parameters. Usage: #[impl_extern_trait(name=\"crate_name\", abi=\"c\")]"
207                .to_string(),
208        );
209    }
210
211    let args_str = args.to_string();
212    let mut name = None;
213    let mut abi = None;
214
215    let parts: Vec<&str> = args_str.split(',').collect();
216
217    for part in parts {
218        let part = part.trim();
219        if part.starts_with("name") {
220            if let Some(start) = part.find('"') {
221                if let Some(end) = part.rfind('"') {
222                    if start < end {
223                        name = Some(part[start + 1..end].to_string());
224                    }
225                }
226            }
227        } else if part.starts_with("abi") {
228            if let Some(start) = part.find('"') {
229                if let Some(end) = part.rfind('"') {
230                    if start < end {
231                        abi = Some(part[start + 1..end].to_string());
232                    }
233                }
234            }
235        }
236    }
237
238    let name = name.ok_or_else(|| {
239        "Missing name parameter. Usage: #[impl_extern_trait(name=\"crate_name\", abi=\"c\")]"
240            .to_string()
241    })?;
242    let abi = abi.unwrap_or_else(|| "c".to_string());
243
244    if abi != "c" && abi != "rust" {
245        return Err("Invalid abi parameter. Supported values: \"c\", \"rust\"".to_string());
246    }
247
248    Ok((name, abi))
249}
250
251/// Implements an extern trait for a type and generates corresponding C function exports.
252///
253/// This macro takes a trait implementation and generates extern "C" functions that can be
254/// called from other languages. Each method in the trait implementation gets a corresponding
255/// extern function with a mangled name based on the crate name and version.
256///
257/// # Arguments
258/// - `name`: The name of the crate that defines the extern trait
259/// - `abi`: The ABI to use for the extern functions ("c" or "rust"), defaults to "c"
260///
261/// # Example
262/// ```rust
263/// struct Calculator;
264///
265/// #[impl_extern_trait(name = "calculator_crate", abi = "c")]
266/// impl MyTrait for Calculator {
267///     fn add(&self, a: i32, b: i32) -> i32 {
268///         a + b
269///     }
270/// }
271/// ```
272///
273/// This will generate extern "C" functions that can be called from other languages.
274#[proc_macro_attribute]
275pub fn impl_extern_trait(args: TokenStream, input: TokenStream) -> TokenStream {
276    let (crate_name_str, abi) = match parse_extern_trait_args(args) {
277        Ok((name, abi)) => (name, abi),
278        Err(error_msg) => {
279            bail!(Span::call_site(), error_msg);
280        }
281    };
282    let input = parse_macro_input!(input as ItemImpl);
283    let mut extern_fn_list = vec![];
284
285    let struct_name = input.self_ty.clone();
286    let trait_name = input.clone().trait_.unwrap().1;
287
288    for item in &input.items {
289        if let syn::ImplItem::Fn(func) = item {
290            let fn_name_raw = &func.sig.ident;
291            let fn_name = extern_fn_name(&crate_name_str, fn_name_raw);
292
293            let inputs = &func.sig.inputs;
294            let output = &func.sig.output;
295            let generics = &func.sig.generics;
296
297            let extern_abi = if abi == "rust" { "Rust" } else { "C" };
298
299            let mut param_names = vec![];
300            let mut param_types = vec![];
301
302            for input in inputs {
303                if let syn::FnArg::Typed(pat_type) = input {
304                    param_names.push(&pat_type.pat);
305                    param_types.push(&pat_type.ty);
306                }
307            }
308
309            extern_fn_list.push(quote! {
310                /// `trait-ffi` generated extern function.
311                #[unsafe(no_mangle)]
312                pub extern #extern_abi fn #fn_name #generics (#inputs) #output {
313                    <#struct_name as #trait_name>::#fn_name_raw(#(#param_names),*)
314                }
315            });
316        }
317    }
318
319    quote! {
320        #input
321        #(#extern_fn_list)*
322    }
323    .into()
324}