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()); }
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
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 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#[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 #[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}