1use crate::util::*;
2use crate::{FfiFunction, FfiType, RustType, TsModule};
3use dodo_bindgen_rust_ir::{ItemFn, Receiver};
4use heck::ToLowerCamelCase as _;
5use proc_macro2::TokenStream;
6use syn::Ident;
7
8use super::ty::TypeExt as _;
9
10#[derive(Clone, Debug, Default)]
11pub struct FunctionDefs {
12 pub functions: Vec<TsMethod>,
13}
14
15#[derive(Clone, Debug)]
16pub struct TsMethod {
17 pub non_blocking: bool,
18 pub ident: Ident,
19 pub inputs: Vec<RustType>,
20 pub output: RustType,
21 pub assoc: Option<Receiver>,
22 pub ffi_ident: Ident,
23 pub self_ty: Option<Ident>,
24}
25
26pub trait ItemFnExt {
31 fn unwrap(self, module: &mut TsModule, class: Option<(Ident, Receiver)>) -> TsMethod;
32}
33
34impl ItemFnExt for ItemFn {
35 fn unwrap(self, module: &mut TsModule, class: Option<(Ident, Receiver)>) -> TsMethod {
36 let mut ffi_inputs = Vec::new();
37 let mut inputs = Vec::new();
38 for arg in self.inputs {
39 ffi_inputs.push(FfiType::from(arg.abi));
40 inputs.push(arg.ty.unwrap(module));
41 }
42
43 let output = self.output.unwrap(module);
44 let ffi_ident = format_ident!("{}", self.symbol);
45
46 module.ffi_lib.interface.push_fn(FfiFunction {
47 ident: ffi_ident.clone(),
48 inputs: ffi_inputs,
49 output: FfiType::from(self.output_abi),
50 non_blocking: self.non_blocking,
51 });
52
53 let (self_ty, assoc) = class.unzip();
54 let method = TsMethod {
55 non_blocking: self.non_blocking,
56 ident: format_ident!("{}", self.name.to_lower_camel_case()),
57 inputs,
58 output,
59 assoc,
60 ffi_ident,
61 self_ty,
62 };
63 module.uses_pin |= method.needs_pin();
64 method
65 }
66}
67
68impl FunctionDefs {
69 pub fn push(&mut self, method: TsMethod) {
70 self.functions.push(method);
71 }
72}
73
74fn return_stmt(output: &RustType) -> TokenStream {
79 match output {
80 RustType::Void => TokenStream::new(),
81 RustType::Numeric(_)
82 | RustType::Boolean
83 | RustType::FnPtr(_)
84 | RustType::Ptr(_)
85 | RustType::PtrMut(_)
86 | RustType::Ref(_)
87 | RustType::RefMut(_)
88 | RustType::Unsupported => quote! {
89 return out! as #output;
90 },
91 RustType::Custom { from_rust, .. } => {
92 let value = raw(from_rust.replace("{}", "out"));
93 quote! {
94 return #value;
95 }
96 },
97 RustType::UserDefined(ident) => quote! {
98 return Reflect.construct(RustPrototype, [out!], #ident) as #ident;
99 },
100 rest => quote! {
101 return new #rest(out!) as #output;
102 },
103 }
104}
105
106impl TsMethod {
107 pub fn needs_pin(&self) -> bool {
108 self.non_blocking
109 && self
110 .inputs
111 .iter()
112 .any(|input| matches!(input, RustType::Custom { .. }))
113 }
114
115 fn is_constructor(&self) -> bool {
116 self.ident == "new"
117 && self.assoc == Some(Receiver::Static)
118 && !self.non_blocking
119 && self
120 .self_ty
121 .as_ref()
122 .is_some_and(|class| self.output == RustType::UserDefined(class.clone()))
123 }
124
125 pub fn print(&self) -> TokenStream {
126 let TsMethod {
127 ident,
128 inputs,
129 output,
130 assoc,
131 ffi_ident,
132 self_ty,
133 non_blocking,
134 } = self;
135
136 let inputs: Vec<&RustType> = inputs.iter().filter(|ty| **ty != RustType::Void).collect();
137 let explicit = match assoc {
138 Some(Receiver::Ref | Receiver::RefMut | Receiver::Owned) => &inputs[1..],
139 Some(Receiver::Static) | None => &inputs[..],
140 };
141
142 let borrow = |sync: TokenStream, async_: TokenStream| {
143 if *non_blocking {
144 quote! { using borrow = await #async_(this); }
145 } else {
146 quote! { using borrow = #sync(this); }
147 }
148 };
149 let mut static_ = TokenStream::new();
150 let mut pre = Vec::new();
151 let mut call_args = Vec::new();
152 match assoc {
153 Some(Receiver::Static) => static_ = quote! { static },
154 Some(Receiver::Ref) => {
155 pre.push(borrow(
156 quote! { __dodo_borrow },
157 quote! { __dodo_borrow_async },
158 ));
159 call_args.push(quote! { borrow.ptr });
160 },
161 Some(Receiver::RefMut) => {
162 pre.push(borrow(
163 quote! { __dodo_borrow_mut },
164 quote! { __dodo_borrow_mut_async },
165 ));
166 call_args.push(quote! { borrow.ptr });
167 },
168 Some(Receiver::Owned) => {
169 pre.push(if *non_blocking {
170 quote! { const ptr = await __dodo_take_async(this); }
171 } else {
172 quote! { const ptr = this.take(); }
173 });
174 call_args.push(quote! { ptr });
175 },
176 None => (),
177 }
178
179 let mut fn_args = Vec::new();
180 let mut pinned = Vec::new();
181 for (i, input) in explicit.iter().enumerate() {
182 let arg = format_ident!("arg_{i}");
183 fn_args.push(quote! { #arg: #input });
184
185 let value = match input {
186 RustType::Numeric(_)
187 | RustType::Boolean
188 | RustType::FnPtr(_)
189 | RustType::Ptr(_)
190 | RustType::PtrMut(_)
191 | RustType::Ref(_)
192 | RustType::RefMut(_)
193 | RustType::Unsupported => quote! { #arg },
194 RustType::Custom { to_rust, .. } => {
195 raw(to_rust.replace("{}", &arg.to_string())).to_token_stream()
196 },
197 _ => quote! { #arg.take() },
198 };
199
200 if *non_blocking && matches!(input, RustType::Custom { .. }) {
201 let temp = format_ident!("pin_{i}");
202 pre.push(quote! { const #temp = #value; });
203 pinned.push(quote! { #temp });
204 pinned.push(quote! { #arg });
205 call_args.push(quote! { #temp });
206 } else {
207 call_args.push(value);
208 }
209 }
210
211 let (async_, await_) = if *non_blocking {
212 (quote! { async }, quote! { await })
213 } else {
214 (TokenStream::new(), TokenStream::new())
215 };
216 let fn_output = match (output, non_blocking) {
217 (RustType::Void, false) => TokenStream::new(),
218 (rest, true) => quote! { : Promise<#rest> },
219 (rest, false) => quote! { : #rest },
220 };
221
222 let call = quote! { #await_ symbols.#ffi_ident(#(#call_args),*) };
223 if self.is_constructor() {
224 return quote! {
225 constructor(#(#fn_args),*) {
226 super(#call);
227 }
228 };
229 }
230 let call = match output {
231 RustType::Void => quote! { #call; },
232 _ => quote! { const out = #call; },
233 };
234 let ret = return_stmt(output);
235
236 let body = if pinned.is_empty() {
237 quote! {
238 #(#pre)*
239 #call
240 #ret
241 }
242 } else {
243 quote! {
244 #(#pre)*
245 const pinned = [#(#pinned),*];
246 __dodo_pinned.add(pinned);
247 try {
248 #call
249 #ret
250 } finally {
251 __dodo_pinned.delete(pinned);
252 }
253 }
254 };
255
256 let head = if self_ty.is_some() {
257 quote! { #static_ #async_ #ident }
258 } else {
259 quote! { export #async_ function #ident }
260 };
261
262 quote! {
263 #head(#(#fn_args),*) #fn_output {
264 #body
265 }
266 }
267 }
268}
269
270impl ToTokens for FunctionDefs {
271 fn to_tokens(&self, tokens: &mut TokenStream) {
272 for method in &self.functions {
273 tokens.extend(method.print());
274 }
275 }
276}
277
278#[cfg(test)]
283mod print_tests {
284 use super::*;
285 use crate::TsModule;
286 use dodo_bindgen_rust_ir::{Abi, Arg, Type, TypeNumeric};
287
288 fn arg(ty: Type, abi: Abi) -> Arg {
289 Arg {
290 name: "arg".to_string(),
291 ty,
292 abi,
293 }
294 }
295
296 fn func(inputs: Vec<Arg>, output: Type, output_abi: Abi) -> ItemFn {
297 ItemFn {
298 name: "test_fn".to_string(),
299 symbol: "__dodo_crate_test_fn".to_string(),
300 doc: String::new(),
301 non_blocking: false,
302 inputs,
303 output,
304 output_abi,
305 }
306 }
307
308 fn print(item: ItemFn) -> String {
309 let mut module = TsModule::default();
310 module.ffi_lib.lib_name = "lib".to_string();
311 let method = item.unwrap(&mut module, None);
312 module.functions.push(method);
313 deno_fmt(format!(
314 "{}\n{}",
315 module.ffi_lib.to_token_stream(),
316 module.functions.to_token_stream()
317 ))
318 }
319
320 #[test]
321 fn test_empty() {
322 insta::assert_snapshot!(print(func(vec![], Type::Void, Abi::Void)));
323 }
324
325 #[test]
326 fn test_return() {
327 let u8_ty = Type::Numeric(TypeNumeric::U8);
328 insta::assert_snapshot!(print(func(
329 vec![arg(u8_ty.clone(), Abi::U8)],
330 u8_ty,
331 Abi::U8
332 )));
333 }
334
335 fn custom(ts_type: &str, to_rust: &str, from_rust: &str) -> Type {
336 Type::Custom {
337 ts_type: ts_type.to_string(),
338 to_rust: to_rust.to_string(),
339 from_rust: from_rust.to_string(),
340 helper: String::new(),
341 }
342 }
343
344 #[test]
345 fn test_many_args() {
346 let custom = Type::Object {
347 module_path: "crate".to_string(),
348 name: "CustomType".to_string(),
349 };
350 insta::assert_snapshot!(print(func(
351 vec![
352 arg(
353 Type::RefMut(Box::new(Type::Slice(Box::new(Type::Numeric(
354 TypeNumeric::U8,
355 ))))),
356 Abi::Pointer,
357 ),
358 arg(Type::Ref(Box::new(Type::Str)), Abi::Pointer),
359 arg(Type::Box(Box::new(custom)), Abi::Pointer),
360 arg(Type::Vec(Box::new(Type::Bool)), Abi::Pointer),
361 ],
362 Type::Void,
363 Abi::Void,
364 )));
365 }
366
367 #[test]
368 fn test_custom() {
369 insta::assert_snapshot!(print(func(
370 vec![arg(
371 custom("string", "`${{}}`.codePointAt(0)!", "''"),
372 Abi::U32,
373 )],
374 custom(
375 "Float64Array<ArrayBuffer>",
376 "",
377 "__dodo_decode_vec({}, Float64Array)",
378 ),
379 Abi::Pointer,
380 )));
381 }
382
383 #[test]
384 fn test_non_blocking_pins_buffers() {
385 let mut item = func(
386 vec![
387 arg(
388 custom("Uint8Array<ArrayBuffer>", "__dodo_view({})", ""),
389 Abi::Buffer,
390 ),
391 arg(custom("string", "__dodo_encode_str({})", ""), Abi::Buffer),
392 ],
393 Type::Void,
394 Abi::Void,
395 );
396 item.non_blocking = true;
397 let mut module = TsModule::default();
398 let method = item.unwrap(&mut module, None);
399 assert!(module.uses_pin);
400 module.functions.push(method);
401 insta::assert_snapshot!(deno_fmt(module.functions.to_token_stream().to_string()));
402 }
403}