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