1use proc_macro::TokenStream;
14use quote::{format_ident, quote};
15use syn::{parse_macro_input, ItemTrait, TraitItem};
16
17fn map_type(ty: &syn::Type) -> Option<&'static str> {
20 let ty_str = quote!(#ty).to_string().replace(' ', "");
21 Some(match ty_str.as_str() {
22 "i8" => "i8",
23 "i16" => "i16",
24 "i32" => "i32",
25 "i64" => "i64",
26 "u8" => "u8",
27 "u16" => "u16",
28 "u32" => "u32",
29 "u64" => "u64",
30 "f32" => "f32",
31 "f64" => "f64",
32 "bool" => "bool",
33 "usize" => "usize",
34 "isize" => "isize",
35 _ => return None,
36 })
37}
38
39struct AttrOpts {
40 name: String,
42}
43
44fn parse_attr_tokens(ts: proc_macro2::TokenStream) -> AttrOpts {
45 let mut name = String::new();
46 let mut iter = ts.into_iter().peekable();
47 while let Some(t) = iter.next() {
48 if let proc_macro2::TokenTree::Ident(id) = t {
49 if id == "name" {
50 for t2 in iter.by_ref() {
51 if let proc_macro2::TokenTree::Literal(lit) = t2 {
52 name = lit.to_string().trim_matches('"').to_string();
53 break;
54 }
55 }
56 }
57 }
58 }
59 if name.is_empty() {
60 panic!("#[abi_vtable] requires `name = \"...\"` (getter symbol prefix)");
61 }
62 AttrOpts { name }
63}
64
65#[proc_macro_attribute]
66pub fn abi_vtable(attr: TokenStream, item: TokenStream) -> TokenStream {
67 let opts = parse_attr_tokens(attr.into());
68 let trait_def = parse_macro_input!(item as ItemTrait);
69 let trait_name = &trait_def.ident;
70 let trait_name_str = trait_name.to_string();
71 let vis = &trait_def.vis;
72
73 struct Method {
75 name: syn::Ident,
76 params: Vec<proc_macro2::TokenStream>, ret: Option<syn::Type>,
78 call_args: Vec<syn::Ident>,
79 }
80
81 let mut methods: Vec<Method> = Vec::new();
82 for item in &trait_def.items {
83 if let TraitItem::Fn(m) = item {
84 let mut params = Vec::new();
85 let mut call_args = Vec::new();
86 for input in &m.sig.inputs {
87 if let syn::FnArg::Typed(pt) = input {
88 let ident = match pt.pat.as_ref() {
89 syn::Pat::Ident(pi) => pi.ident.clone(),
90 _ => continue,
91 };
92 let ty_str = quote!(#pt.ty).to_string().replace(' ', "");
93 let c_ty = map_type(&pt.ty).unwrap_or_else(|| {
94 panic!(
95 "abi_vtable: unsupported param type `{}` in `{}::{}` — only C-representable scalars cross the boundary",
96 ty_str, trait_name_str, m.sig.ident
97 )
98 });
99 let c_ty: syn::Type = syn::parse_str(c_ty).unwrap();
100 params.push(quote!(#ident: #c_ty));
101 call_args.push(ident);
102 }
103 }
104 let ret = match &m.sig.output {
105 syn::ReturnType::Type(_, ty) => {
106 let ty_str = quote!(#ty).to_string().replace(' ', "");
107 let c_ty = map_type(ty).unwrap_or_else(|| {
108 panic!(
109 "abi_vtable: unsupported return type `{}` in `{}::{}`",
110 ty_str, trait_name_str, m.sig.ident
111 )
112 });
113 Some(syn::parse_str::<syn::Type>(c_ty).unwrap())
114 }
115 syn::ReturnType::Default => None,
116 };
117 methods.push(Method {
118 name: m.sig.ident.clone(),
119 params,
120 ret,
121 call_args,
122 });
123 }
124 }
125
126 let vtable_struct_name = format_ident!("{}Vtable", trait_name);
128
129 let mut struct_fields = Vec::new();
130 let mut thunks = Vec::new();
133 let mut thunk_assignments = Vec::new();
134
135 for m in &methods {
136 let fname = &m.name;
137 let thunk_name = format_ident!("{}_thunk", m.name);
138 let arg_tys = &m.params;
139 let ret = match &m.ret {
140 Some(t) => quote!(-> #t),
141 None => quote!(),
142 };
143
144 struct_fields.push(quote! {
145 pub #fname: unsafe extern "C" fn(ctx: *mut ::std::ffi::c_void, #(#arg_tys),*) #ret
146 });
147
148 let call_args = &m.call_args;
149 thunks.push(quote! {
152 unsafe extern "C" fn #thunk_name(
153 ctx: *mut ::std::ffi::c_void,
154 #(#arg_tys),*
155 ) #ret {
156 unsafe { (&*(ctx as *const $impl_ty)).#fname(#(#call_args),*) }
157 }
158 });
159 thunk_assignments.push(quote! { #fname: #thunk_name });
160 }
161
162 let static_name = format_ident!("{}_VTABLE", opts.name.to_uppercase());
163 let getter_name = format_ident!("{}_get_vtable", opts.name);
164 let instance_name = format_ident!("{}_INSTANCE", opts.name.to_uppercase());
165 let impl_macro = format_ident!("abi_vtable_impl_{}", trait_name_str.to_lowercase());
166
167 let expanded = quote! {
168 #trait_def
169
170 #[repr(C)]
173 #[derive(Clone, Copy)]
174 #vis struct #vtable_struct_name {
175 #(#struct_fields,)*
176 }
177
178 #[macro_export]
183 macro_rules! #impl_macro {
184 ($impl_ty:ty, $instance:expr) => {
186 static #instance_name: $impl_ty = $instance;
187
188 #(
190 #thunks
191 )*
192
193 static #static_name: #vtable_struct_name = #vtable_struct_name {
194 #(#thunk_assignments,)*
195 };
196
197 #[no_mangle]
198 pub extern "C" fn #getter_name() -> *const #vtable_struct_name {
199 &#static_name
200 }
201 };
202 }
203 };
204
205 TokenStream::from(expanded)
206}