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            /// `trait-ffi` generated.
186            pub fn ____checker_do_not_use(){
187                unsafe extern "C" {
188                    fn #warn_fn_name();
189                }
190                unsafe { #warn_fn_name() };
191            }
192            #(#fn_list)*
193        }
194
195        #generated_macro
196    }
197    .into()
198}
199
200fn parse_extern_trait_args(args: TokenStream) -> Result<(String, String), String> {
201    if args.is_empty() {
202        return Err(
203            "Missing parameters. Usage: #[impl_extern_trait(name=\"crate_name\", abi=\"c\")]"
204                .to_string(),
205        );
206    }
207
208    let args_str = args.to_string();
209    let mut name = None;
210    let mut abi = None;
211
212    let parts: Vec<&str> = args_str.split(',').collect();
213
214    for part in parts {
215        let part = part.trim();
216        if part.starts_with("name") {
217            if let Some(start) = part.find('"') {
218                if let Some(end) = part.rfind('"') {
219                    if start < end {
220                        name = Some(part[start + 1..end].to_string());
221                    }
222                }
223            }
224        } else if part.starts_with("abi") {
225            if let Some(start) = part.find('"') {
226                if let Some(end) = part.rfind('"') {
227                    if start < end {
228                        abi = Some(part[start + 1..end].to_string());
229                    }
230                }
231            }
232        }
233    }
234
235    let name = name.ok_or_else(|| {
236        "Missing name parameter. Usage: #[impl_extern_trait(name=\"crate_name\", abi=\"c\")]"
237            .to_string()
238    })?;
239    let abi = abi.unwrap_or_else(|| "c".to_string());
240
241    if abi != "c" && abi != "rust" {
242        return Err("Invalid abi parameter. Supported values: \"c\", \"rust\"".to_string());
243    }
244
245    Ok((name, abi))
246}
247
248/// Implements an extern trait for a type and generates corresponding C function exports.
249///
250/// This macro takes a trait implementation and generates extern "C" functions that can be
251/// called from other languages. Each method in the trait implementation gets a corresponding
252/// extern function with a mangled name based on the crate name and version.
253///
254/// # Arguments
255/// - `name`: The name of the crate that defines the extern trait
256/// - `abi`: The ABI to use for the extern functions ("c" or "rust"), defaults to "c"
257///
258/// # Example
259/// ```rust
260/// struct Calculator;
261///
262/// #[impl_extern_trait(name = "calculator_crate", abi = "c")]
263/// impl MyTrait for Calculator {
264///     fn add(&self, a: i32, b: i32) -> i32 {
265///         a + b
266///     }
267/// }
268/// ```
269///
270/// This will generate extern "C" functions that can be called from other languages.
271#[proc_macro_attribute]
272pub fn impl_extern_trait(args: TokenStream, input: TokenStream) -> TokenStream {
273    let (crate_name_str, abi) = match parse_extern_trait_args(args) {
274        Ok((name, abi)) => (name, abi),
275        Err(error_msg) => {
276            bail!(Span::call_site(), error_msg);
277        }
278    };
279    let input = parse_macro_input!(input as ItemImpl);
280    let mut extern_fn_list = vec![];
281
282    let struct_name = input.self_ty.clone();
283    let trait_name = input.clone().trait_.unwrap().1;
284
285    for item in &input.items {
286        if let syn::ImplItem::Fn(func) = item {
287            let fn_name_raw = &func.sig.ident;
288            let fn_name = extern_fn_name(&crate_name_str, fn_name_raw);
289
290            let inputs = &func.sig.inputs;
291            let output = &func.sig.output;
292
293            let extern_abi = if abi == "rust" { "Rust" } else { "C" };
294
295            let mut param_names = vec![];
296            let mut param_types = vec![];
297
298            for input in inputs {
299                if let syn::FnArg::Typed(pat_type) = input {
300                    param_names.push(&pat_type.pat);
301                    param_types.push(&pat_type.ty);
302                }
303            }
304
305            extern_fn_list.push(quote! {
306                /// `trait-ffi` generated extern function.
307                #[unsafe(no_mangle)]
308                pub extern #extern_abi fn #fn_name(#inputs) #output {
309                    <#struct_name as #trait_name>::#fn_name_raw(#(#param_names),*)
310                }
311            });
312        }
313    }
314
315    quote! {
316        #input
317        #(#extern_fn_list)*
318    }
319    .into()
320}