Skip to main content

dodo_bindgen_codegen/
fn_.rs

1use 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
25/* -------------------------------------------------------------------------- */
26
27// MARK: transform
28
29pub 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        // transform types
36
37        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        // code generation
55        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
80/* -------------------------------------------------------------------------- */
81
82// MARK: print
83
84impl 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/* -------------------------------------------------------------------------- */
244
245// MARK: print tests
246
247#[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}