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 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()); }
45
46 let args_str = args.to_string();
47 let mut abi = None;
48
49 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#[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 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#[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 #[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}