dodo_bindgen_codegen/
fn_.rs1use crate::util::*;
2use crate::{FfiFunction, RustType, TsModule};
3use dodo_bindgen_rust_ir::{Association, Attribute, ItemFn};
4use proc_macro2::TokenStream;
5use syn::Ident;
6
7use super::ty::TypeExt as _;
8
9#[derive(Clone, Debug, Default)]
10pub struct FunctionDefs {
11 pub functions: Vec<TsMethod>,
12}
13
14#[derive(Clone, Debug)]
15pub struct TsMethod {
16 pub attr: Attribute,
17 pub ident: Ident,
18 pub inputs: Vec<RustType>,
19 pub output: RustType,
20 pub assoc: Option<Association>,
21 pub ffi_ident: Ident,
22 pub self_ty: Option<Ident>,
23}
24
25pub trait ItemFnExt {
30 fn unwrap(self, module: &mut TsModule) -> TsMethod;
31}
32
33impl ItemFnExt for ItemFn {
34 fn unwrap(self, module: &mut TsModule) -> TsMethod {
35 let mut ffi_inputs = Vec::new();
38 let mut inputs = Vec::new();
39 for input in self.inputs {
40 let (ffi_input, input) = input.unwrap(module);
41 ffi_inputs.push(ffi_input);
42 inputs.push(input);
43 }
44
45 let (ffi_output, output) = self.output.unwrap(module);
46
47 let ident = self.ident;
48 let ffi_ident = if let Some(self_ty) = &self.block.self_ty {
49 format_ident!("__{}__{ident}", self_ty.to_string())
50 } else {
51 format_ident!("__{ident}")
52 };
53
54 module.ffi_lib.interface.push_fn(FfiFunction {
56 ident: ffi_ident.clone(),
57 inputs: ffi_inputs,
58 output: ffi_output,
59 non_blocking: self.attr.has_non_blocking(),
60 });
61
62 TsMethod {
63 attr: self.attr,
64 ident,
65 inputs,
66 output,
67 assoc: self.assoc.clone(),
68 ffi_ident,
69 self_ty: self.block.self_ty,
70 }
71 }
72}
73
74impl FunctionDefs {
75 pub fn push(&mut self, method: TsMethod) {
76 self.functions.push(method);
77 }
78}
79
80impl TsMethod {
85 pub fn print(&self) -> TokenStream {
86 let TsMethod {
87 ident,
88 inputs,
89 output,
90 assoc,
91 ffi_ident,
92 self_ty,
93 ..
94 } = self;
95
96 let inputs: Vec<&RustType> = inputs.iter().filter(|ty| **ty != RustType::Void).collect();
97
98 let mut fn_args = Vec::new();
99 let mut call_args = Vec::new();
100
101 if !inputs.is_empty() {
102 let inputs_slice = if let Some(assoc) = assoc {
103 if self_ty.is_some() {
104 match assoc {
105 Association::Static => inputs.as_slice(),
106 _ => &inputs[1..inputs.len()],
107 }
108 } else {
109 panic!("this method has an association but is missing a self type");
110 }
111 } else {
112 inputs.as_slice()
113 };
114
115 for (i, input) in inputs_slice.iter().enumerate() {
116 let fn_arg = format_ident!("arg_{i}");
117 fn_args.push(quote! { #fn_arg: #input });
118
119 match input {
120 RustType::Void => (),
121 rest => call_args.push(match rest {
122 RustType::Numeric(_)
123 | RustType::Boolean
124 | RustType::FnPtr(_)
125 | RustType::Ptr(_)
126 | RustType::PtrMut(_)
127 | RustType::Ref(_)
128 | RustType::RefMut(_)
129 | RustType::Unsupported => quote! { #fn_arg },
130 RustType::Char => quote! { #fn_arg.get() },
131 _ => quote! { #fn_arg.take() },
132 }),
133 };
134 }
135 }
136
137 let mut stmts = TokenStream::new();
138
139 let mut ident = quote! { #ident };
140
141 let fn_output;
142 let call_expr = match output {
143 RustType::Void => {
144 fn_output = TokenStream::new();
145 quote! { symbols.#ffi_ident }
146 },
147 rest => {
148 if self.attr.has_non_blocking() {
149 ident = quote! { async #ident };
150 fn_output = quote! { : Promise<#rest> };
151 quote! { const out = await symbols.#ffi_ident }
152 } else {
153 fn_output = quote! { : #rest };
154 quote! { const out = symbols.#ffi_ident }
155 }
156 },
157 };
158
159 let generate_return_stmt = || -> TokenStream {
160 match &output {
161 RustType::Void => TokenStream::new(),
162 RustType::Numeric(_)
163 | RustType::Boolean
164 | RustType::FnPtr(_)
165 | RustType::Ptr(_)
166 | RustType::PtrMut(_)
167 | RustType::Ref(_)
168 | RustType::RefMut(_)
169 | RustType::Unsupported => quote! {
170 return out! as #output;
171 },
172 rest => quote! {
173 return new #rest(out!) as #output;
174 },
175 }
176 };
177
178 if let Some(assoc) = assoc {
179 stmts.extend(match assoc {
180 Association::Static => {
181 ident = quote! { static #ident };
182 quote! {
183 #call_expr(#(#call_args),*);
184 }
185 },
186
187 Association::Instance => {
188 quote! {
189 #call_expr(this.ptr, #(#call_args),*);
190 }
191 },
192 Association::InstanceMut => {
193 quote! {
194 const ptr = this.ptr;
195 this.ptr = null;
196 #call_expr(ptr, #(#call_args),*)!;
197 this.ptr = ptr;
198 }
199 },
200 Association::Destructor => {
201 quote! {
202 const ptr = this.ptr;
203 this.ptr = null;
204 #call_expr(ptr, #(#call_args),*);
205 }
206 },
207 });
208 stmts.extend(generate_return_stmt());
209 } else {
210 stmts.extend(quote! {
211 #call_expr(#(#call_args),*);
212 });
213 stmts.extend(generate_return_stmt());
214 }
215
216 quote! {
217 #ident(#(#fn_args),*) #fn_output {
218 #stmts
219 }
220
221 }
222 }
223}
224
225impl ToTokens for FunctionDefs {
226 fn to_tokens(&self, tokens: &mut TokenStream) {
227 for method in &self.functions {
228 let async_ = if method.attr.has_non_blocking() {
229 quote! { async }
230 } else {
231 TokenStream::new()
232 };
233
234 let method = method.print();
235
236 tokens.extend(quote! {
237 export #async_ function #method
238 });
239 }
240 }
241}
242
243#[cfg(test)]
248mod print_tests {
249 use super::*;
250 use crate::{TsFormat, TsModule};
251 use dodo_bindgen_rust_ir::{parse_quote, prettify};
252
253 macro_rules! test_transform {
254 ($($tt:tt)*) => {
255 println!("[rust source]\n{}", prettify!(stringify!($($tt)*)));
256
257 let source = parse_quote!(ItemFn, $($tt)*);
258
259 let mut export = source.clone();
260 export.transform();
261 println!("[rust wrapper]\n{}", prettify!(export.to_token_stream().to_string().as_str()));
262
263 let mut module = TsModule::default();
264 let method = source.unwrap(&mut module);
265 module.functions.push(method);
266
267 let ffi_lib = TsFormat::format(module.ffi_lib.to_token_stream().to_string());
268 println!("[ts ffi]\n{}", ffi_lib);
269
270 let functions = TsFormat::format(module.functions.to_token_stream().to_string());
271 println!("[ts mod]\n{}", functions);
272 };
273 }
274
275 #[test]
276 fn test_empty() {
277 test_transform!(
278 fn test_fn() {}
279 );
280 }
281
282 #[test]
283 fn test_u8() {
284 test_transform!(
285 fn test_fn(arg0: u8) {}
286 );
287 }
288
289 #[test]
290 fn test_return() {
291 test_transform!(
292 fn test_fn(arg0: u8) -> u8 {}
293 );
294 }
295
296 #[test]
297 fn test_many_args() {
298 test_transform!(
299 fn test_fn(arg0: &mut [u8], arg1: &str, arg2: Box<CustomType>, arg3: Vec<String>) {}
300 );
301 }
302}