napi_derive_backend/codegen/
fn.rs

1use proc_macro2::{Ident, Span, TokenStream};
2use quote::ToTokens;
3use syn::spanned::Spanned;
4
5use crate::{
6  codegen::{get_intermediate_ident, js_mod_to_token_stream},
7  BindgenResult, CallbackArg, Diagnostic, FnKind, FnSelf, NapiFn, NapiFnArgKind, TryToTokens,
8  TYPEDARRAY_SLICE_TYPES,
9};
10
11impl TryToTokens for NapiFn {
12  fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> {
13    let name_str = self.name.to_string();
14    let intermediate_ident = get_intermediate_ident(&name_str);
15    let args_len = self.args.len();
16
17    let ArgConversions {
18      arg_conversions,
19      args: arg_names,
20      refs,
21      mut_ref_spans,
22      unsafe_,
23    } = self.gen_arg_conversions()?;
24    // The JS engine can't properly track mutability in an async context, so refuse to compile
25    // code that tries to use async and mutability together without `unsafe` mark.
26    if self.is_async && !mut_ref_spans.is_empty() && !unsafe_ {
27      return Diagnostic::from_vec(
28        mut_ref_spans
29          .into_iter()
30          .map(|s| Diagnostic::span_error(s, "mutable reference is unsafe with async"))
31          .collect(),
32      );
33    }
34    if Some(FnSelf::MutRef) == self.fn_self && self.is_async && !self.unsafe_ {
35      return Err(Diagnostic::span_error(
36        self.name.span(),
37        "&mut self in async napi methods should be marked as unsafe",
38      ));
39    }
40    let arg_ref_count = refs.len();
41    let receiver = self.gen_fn_receiver();
42    let receiver_ret_name = Ident::new("_ret", Span::call_site());
43    let ret = self.gen_fn_return(&receiver_ret_name);
44    let register = self.gen_fn_register();
45    let attrs = &self.attrs;
46
47    let build_ref_container = if self.is_async {
48      quote! {
49          struct NapiRefContainer([napi::sys::napi_ref; #arg_ref_count]);
50          impl NapiRefContainer {
51            fn drop(self, env: napi::sys::napi_env) {
52              for r in self.0.into_iter() {
53                assert_eq!(
54                  unsafe { napi::sys::napi_reference_unref(env, r, &mut 0) },
55                  napi::sys::Status::napi_ok,
56                  "failed to delete napi ref"
57                );
58                assert_eq!(
59                  unsafe { napi::sys::napi_delete_reference(env, r) },
60                  napi::sys::Status::napi_ok,
61                  "failed to delete napi ref"
62                );
63              }
64            }
65          }
66          unsafe impl Send for NapiRefContainer {}
67          unsafe impl Sync for NapiRefContainer {}
68          let _make_ref = |a: ::std::ptr::NonNull<napi::bindgen_prelude::sys::napi_value__>| {
69            let mut node_ref = ::std::mem::MaybeUninit::uninit();
70            napi::bindgen_prelude::check_status!(unsafe {
71                napi::bindgen_prelude::sys::napi_create_reference(env, a.as_ptr(), 1, node_ref.as_mut_ptr())
72              },
73              "failed to create napi ref"
74            )?;
75            Ok::<napi::sys::napi_ref, napi::Error>(unsafe { node_ref.assume_init() })
76          };
77          let mut _args_array = [::std::ptr::null_mut::<napi::bindgen_prelude::sys::napi_ref__>(); #arg_ref_count];
78          let mut _arg_write_index = 0;
79
80          #(#refs)*
81
82          #[cfg(debug_assertions)]
83          {
84              for a in &_args_array {
85                assert!(!a.is_null(), "failed to initialize napi ref");
86              }
87          }
88          let _args_ref = NapiRefContainer(_args_array);
89      }
90    } else {
91      quote! {}
92    };
93    let native_call = if !self.is_async {
94      quote! {
95        napi::bindgen_prelude::within_runtime_if_available(move || {
96          let #receiver_ret_name = {
97            #receiver(#(#arg_names),*)
98          };
99          #ret
100        })
101      }
102    } else {
103      let call = if self.is_ret_result {
104        quote! { #receiver(#(#arg_names),*).await }
105      } else {
106        quote! { Ok(#receiver(#(#arg_names),*).await) }
107      };
108      quote! {
109        napi::bindgen_prelude::execute_tokio_future(env, async move { #call }, move |env, #receiver_ret_name| {
110          _args_ref.drop(env);
111          #ret
112        })
113      }
114    };
115
116    // async factory only
117    let use_after_async = if self.is_async && self.parent.is_some() && self.fn_self.is_none() {
118      quote! { true }
119    } else {
120      quote! { false }
121    };
122
123    let function_call_inner = quote! {
124      napi::bindgen_prelude::CallbackInfo::<#args_len>::new(env, cb, None, #use_after_async).and_then(|mut cb| {
125          #build_ref_container
126          #(#arg_conversions)*
127          #native_call
128        })
129    };
130
131    let function_call = if args_len == 0
132      && self.fn_self.is_none()
133      && self.kind != FnKind::Constructor
134      && self.kind != FnKind::Factory
135      && !self.is_async
136    {
137      quote! { #native_call }
138    } else if self.kind == FnKind::Constructor {
139      let return_from_factory = if self.catch_unwind {
140        quote! { return Ok(std::ptr::null_mut()); }
141      } else {
142        quote! { return std::ptr::null_mut(); }
143      };
144      quote! {
145        // constructor function is called from class `factory`
146        // so we should skip the original `constructor` logic
147        if napi::__private::___CALL_FROM_FACTORY.with(|inner| inner.load(std::sync::atomic::Ordering::Relaxed)) {
148            #return_from_factory
149        }
150        #function_call_inner
151      }
152    } else {
153      function_call_inner
154    };
155
156    let function_call = if self.catch_unwind {
157      quote! {
158        {
159          std::panic::catch_unwind(|| { #function_call })
160            .map_err(|e| {
161              let message = {
162                if let Some(string) = e.downcast_ref::<String>() {
163                  string.clone()
164                } else if let Some(string) = e.downcast_ref::<&str>() {
165                  string.to_string()
166                } else {
167                  format!("panic from Rust code: {:?}", e)
168                }
169              };
170              napi::Error::new(napi::Status::GenericFailure, message)
171            })
172            .and_then(|r| r)
173        }
174      }
175    } else {
176      quote! {
177        #function_call
178      }
179    };
180
181    (quote! {
182      #(#attrs)*
183      #[doc(hidden)]
184      #[allow(non_snake_case)]
185      #[allow(clippy::all)]
186      extern "C" fn #intermediate_ident(
187        env: napi::bindgen_prelude::sys::napi_env,
188        cb: napi::bindgen_prelude::sys::napi_callback_info
189      ) -> napi::bindgen_prelude::sys::napi_value {
190        unsafe {
191          #function_call.unwrap_or_else(|e| {
192            napi::bindgen_prelude::JsError::from(e).throw_into(env);
193            std::ptr::null_mut::<napi::bindgen_prelude::sys::napi_value__>()
194          })
195        }
196      }
197
198      #register
199    })
200    .to_tokens(tokens);
201
202    Ok(())
203  }
204}
205
206impl NapiFn {
207  fn gen_arg_conversions(&self) -> BindgenResult<ArgConversions> {
208    let mut arg_conversions = vec![];
209    let mut args = vec![];
210    let mut refs = vec![];
211    let mut mut_ref_spans = vec![];
212    let make_ref = |input| {
213      quote! {
214        _args_array[_arg_write_index] = _make_ref(
215          ::std::ptr::NonNull::new(#input)
216            .ok_or_else(|| napi::Error::new(napi::Status::InvalidArg, "referenced ptr is null".to_owned()))?
217        )?;
218        _arg_write_index += 1;
219      }
220    };
221
222    // fetch this
223    if let Some(parent) = &self.parent {
224      match self.fn_self {
225        Some(FnSelf::Ref) => {
226          refs.push(make_ref(quote! { cb.this }));
227          arg_conversions.push(quote! {
228            let this_ptr = unsafe { cb.unwrap_raw::<#parent>()? };
229            let this: &#parent = Box::leak(Box::from_raw(this_ptr));
230          });
231        }
232        Some(FnSelf::MutRef) => {
233          refs.push(make_ref(quote! { cb.this }));
234          arg_conversions.push(quote! {
235            let this_ptr = unsafe { cb.unwrap_raw::<#parent>()? };
236            let this: &mut #parent = Box::leak(Box::from_raw(this_ptr));
237          });
238        }
239        _ => {}
240      };
241    }
242
243    let mut skipped_arg_count = 0;
244    for (i, arg) in self.args.iter().enumerate() {
245      let i = i - skipped_arg_count;
246      let ident = Ident::new(&format!("arg{}", i), Span::call_site());
247
248      match &arg.kind {
249        NapiFnArgKind::PatType(path) => {
250          if &path.ty.to_token_stream().to_string() == "Env" {
251            args.push(quote! { napi::bindgen_prelude::Env::from(env) });
252            skipped_arg_count += 1;
253          } else {
254            let is_in_class = self.parent.is_some();
255            if let syn::Type::Path(path) = path.ty.as_ref() {
256              if let Some(p) = path.path.segments.last() {
257                if p.ident == "Reference" {
258                  if !is_in_class {
259                    bail_span!(p, "`Reference` is only allowed in class methods");
260                  }
261                  if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
262                    args: angle_bracketed_args,
263                    ..
264                  }) = &p.arguments
265                  {
266                    if let Some(syn::GenericArgument::Type(syn::Type::Path(path))) =
267                      angle_bracketed_args.first()
268                    {
269                      if let Some(p) = path.path.segments.first() {
270                        if p.ident == *self.parent.as_ref().unwrap() {
271                          args.push(quote! {
272                            napi::bindgen_prelude::Reference::from_value_ptr(this_ptr.cast(), env)?
273                          });
274                          skipped_arg_count += 1;
275                          continue;
276                        }
277                      }
278                    }
279                  }
280                } else if p.ident == "This" {
281                  if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
282                    args: angle_bracketed_args,
283                    ..
284                  }) = &p.arguments
285                  {
286                    if let Some(syn::GenericArgument::Type(generic_type)) =
287                      angle_bracketed_args.first()
288                    {
289                      if let syn::Type::Path(syn::TypePath {
290                        path: syn::Path { segments, .. },
291                        ..
292                      }) = generic_type
293                      {
294                        if let Some(syn::PathSegment { ident, .. }) = segments.first() {
295                          if let Some((primitive_type, _)) =
296                            crate::PRIMITIVE_TYPES.iter().find(|(p, _)| ident == *p)
297                          {
298                            bail_span!(
299                              ident,
300                              "This type must not be {} \nthis in JavaScript function must be `Object` type or `undefined`",
301                              primitive_type
302                            );
303                          }
304                          args.push(
305                            quote! {
306                              {
307                                <#ident as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb.this)?
308                              }
309                            },
310                          );
311                          skipped_arg_count += 1;
312                          continue;
313                        }
314                      } else if let syn::Type::Reference(syn::TypeReference {
315                        elem,
316                        mutability,
317                        ..
318                      }) = generic_type
319                      {
320                        if let syn::Type::Path(syn::TypePath {
321                          path: syn::Path { segments, .. },
322                          ..
323                        }) = elem.as_ref()
324                        {
325                          if let Some(syn::PathSegment { ident, .. }) = segments.first() {
326                            refs.push(make_ref(quote! { cb.this }));
327                            let token = if mutability.is_some() {
328                              mut_ref_spans.push(generic_type.span());
329                              quote! { <#ident as napi::bindgen_prelude::FromNapiMutRef>::from_napi_mut_ref(env, cb.this)? }
330                            } else {
331                              quote! { <#ident as napi::bindgen_prelude::FromNapiRef>::from_napi_ref(env, cb.this)? }
332                            };
333                            args.push(token);
334                            skipped_arg_count += 1;
335                            continue;
336                          }
337                        }
338                      }
339                    }
340                  }
341                  refs.push(make_ref(quote! { cb.this }));
342                  args.push(quote! { <napi::bindgen_prelude::This as napi::NapiValue>::from_raw_unchecked(env, cb.this) });
343                  skipped_arg_count += 1;
344                  continue;
345                }
346              }
347            }
348            let (arg_conversion, arg_type) = self.gen_ty_arg_conversion(&ident, i, path)?;
349            if NapiArgType::MutRef == arg_type {
350              mut_ref_spans.push(path.ty.span());
351            }
352            if arg_type.is_ref() {
353              refs.push(make_ref(quote! { cb.get_arg(#i) }));
354            }
355            arg_conversions.push(arg_conversion);
356            args.push(quote! { #ident });
357          }
358        }
359        NapiFnArgKind::Callback(cb) => {
360          arg_conversions.push(self.gen_cb_arg_conversion(&ident, i, cb));
361          args.push(quote! { #ident });
362        }
363      }
364    }
365
366    Ok(ArgConversions {
367      arg_conversions,
368      args,
369      refs,
370      mut_ref_spans,
371      unsafe_: self.unsafe_,
372    })
373  }
374
375  /// Returns a type conversion, and a boolean indicating whether this value needs to have a reference created to extend the lifetime
376  /// for async functions.
377  fn gen_ty_arg_conversion(
378    &self,
379    arg_name: &Ident,
380    index: usize,
381    path: &syn::PatType,
382  ) -> BindgenResult<(TokenStream, NapiArgType)> {
383    let ty = &*path.ty;
384    let type_check = if self.return_if_invalid {
385      quote! {
386        if let Ok(maybe_promise) = <#ty as napi::bindgen_prelude::ValidateNapiValue>::validate(env, cb.get_arg(#index)) {
387          if !maybe_promise.is_null() {
388            return Ok(maybe_promise);
389          }
390        } else {
391          return Ok(std::ptr::null_mut());
392        }
393      }
394    } else if self.strict {
395      quote! {
396        let maybe_promise = <#ty as napi::bindgen_prelude::ValidateNapiValue>::validate(env, cb.get_arg(#index))?;
397        if !maybe_promise.is_null() {
398          return Ok(maybe_promise);
399        }
400      }
401    } else {
402      quote! {}
403    };
404
405    match ty {
406      syn::Type::Reference(syn::TypeReference {
407        lifetime: Some(lifetime),
408        ..
409      }) => Err(Diagnostic::span_error(
410        lifetime.span(),
411        "lifetime is not allowed in napi function arguments",
412      )),
413      syn::Type::Reference(syn::TypeReference {
414        mutability: Some(_),
415        elem,
416        ..
417      }) => {
418        let q = quote! {
419          let #arg_name = {
420            #type_check
421            <#elem as napi::bindgen_prelude::FromNapiMutRef>::from_napi_mut_ref(env, cb.get_arg(#index))?
422          };
423        };
424        Ok((q, NapiArgType::MutRef))
425      }
426      syn::Type::Reference(syn::TypeReference {
427        mutability, elem, ..
428      }) => {
429        if let syn::Type::Slice(slice) = &**elem {
430          if let syn::Type::Path(ele) = &*slice.elem {
431            if let Some(syn::PathSegment { ident, .. }) = ele.path.segments.first() {
432              if TYPEDARRAY_SLICE_TYPES.contains_key(&&*ident.to_string()) {
433                let q = quote! {
434                  let #arg_name = {
435                    #type_check
436                    <&mut #elem as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb.get_arg(#index))?
437                  };
438                };
439                return Ok((q, NapiArgType::Ref));
440              }
441            }
442          }
443        }
444        let q = if mutability.is_some() {
445          quote! {
446            let #arg_name = {
447              #type_check
448              <#elem as napi::bindgen_prelude::FromNapiMutRef>::from_napi_mut_ref(env, cb.get_arg(#index))?
449            }
450          }
451        } else {
452          quote! {
453            let #arg_name = {
454              #type_check
455              <#elem as napi::bindgen_prelude::FromNapiRef>::from_napi_ref(env, cb.get_arg(#index))?
456            };
457          }
458        };
459        Ok((
460          q,
461          if mutability.is_some() {
462            NapiArgType::MutRef
463          } else {
464            NapiArgType::Ref
465          },
466        ))
467      }
468      _ => {
469        let q = quote! {
470          let #arg_name = {
471            #type_check
472            <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb.get_arg(#index))?
473          };
474        };
475        Ok((q, NapiArgType::Value))
476      }
477    }
478  }
479
480  fn gen_cb_arg_conversion(&self, arg_name: &Ident, index: usize, cb: &CallbackArg) -> TokenStream {
481    let mut inputs = vec![];
482    let mut arg_conversions = vec![];
483
484    for (i, ty) in cb.args.iter().enumerate() {
485      let cb_arg_ident = Ident::new(&format!("callback_arg_{}", i), Span::call_site());
486      inputs.push(quote! { #cb_arg_ident: #ty });
487      arg_conversions.push(
488        quote! { <#ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #cb_arg_ident)? },
489      );
490    }
491
492    let ret = match &cb.ret {
493      Some(ty) => {
494        quote! {
495          let ret = <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, ret_ptr)?;
496
497          Ok(ret)
498        }
499      }
500      None => quote! { Ok(()) },
501    };
502
503    quote! {
504      napi::bindgen_prelude::assert_type_of!(env, cb.get_arg(#index), napi::bindgen_prelude::ValueType::Function)?;
505      let #arg_name = |#(#inputs),*| {
506        let args = vec![
507          #(#arg_conversions),*
508        ];
509
510        let mut ret_ptr = std::ptr::null_mut();
511
512        napi::bindgen_prelude::check_pending_exception!(
513          env,
514          napi::bindgen_prelude::sys::napi_call_function(
515            env,
516            cb.this(),
517            cb.get_arg(#index),
518            args.len(),
519            args.as_ptr(),
520            &mut ret_ptr
521          )
522        )?;
523
524        #ret
525      };
526    }
527  }
528
529  fn gen_fn_receiver(&self) -> TokenStream {
530    let name = &self.name;
531
532    match self.fn_self {
533      Some(FnSelf::Value) => {
534        // impossible, panic! in parser
535        unreachable!();
536      }
537      Some(FnSelf::Ref) | Some(FnSelf::MutRef) => quote! { this.#name },
538      None => match &self.parent {
539        Some(class) => quote! { #class::#name },
540        None => quote! { #name },
541      },
542    }
543  }
544
545  fn gen_fn_return(&self, ret: &Ident) -> TokenStream {
546    let js_name = &self.js_name;
547
548    if let Some(ty) = &self.ret {
549      let ty_string = ty.into_token_stream().to_string();
550      let is_return_self = ty_string == "& Self" || ty_string == "&mut Self";
551      if self.kind == FnKind::Constructor {
552        let parent = self
553          .parent
554          .as_ref()
555          .expect("Parent must exist for constructor");
556        if self.is_ret_result {
557          if self.parent_is_generator {
558            quote! { cb.construct_generator::<false, #parent>(#js_name, #ret?) }
559          } else {
560            quote! {
561              match #ret {
562                Ok(value) => {
563                  cb.construct::<false, #parent>(#js_name, value)
564                }
565                Err(err) => {
566                  napi::bindgen_prelude::JsError::from(err).throw_into(env);
567                  Ok(std::ptr::null_mut())
568                }
569              }
570            }
571          }
572        } else if self.parent_is_generator {
573          quote! { cb.construct_generator::<false, #parent>(#js_name, #ret) }
574        } else {
575          quote! { cb.construct::<false, #parent>(#js_name, #ret) }
576        }
577      } else if self.kind == FnKind::Factory {
578        if self.is_ret_result {
579          if self.parent_is_generator {
580            quote! { cb.generator_factory(#js_name, #ret?) }
581          } else if self.is_async {
582            quote! { cb.factory(#js_name, #ret) }
583          } else {
584            quote! {
585              match #ret {
586                Ok(value) => {
587                  cb.factory(#js_name, value)
588                }
589                Err(err) => {
590                  napi::bindgen_prelude::JsError::from(err).throw_into(env);
591                  Ok(std::ptr::null_mut())
592                }
593              }
594            }
595          }
596        } else if self.parent_is_generator {
597          quote! { cb.generator_factory(#js_name, #ret) }
598        } else {
599          quote! { cb.factory(#js_name, #ret) }
600        }
601      } else if self.is_ret_result {
602        if self.is_async {
603          quote! {
604            <#ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #ret)
605          }
606        } else if is_return_self {
607          quote! { #ret.map(|_| cb.this) }
608        } else {
609          quote! {
610            match #ret {
611              Ok(value) => napi::bindgen_prelude::ToNapiValue::to_napi_value(env, value),
612              Err(err) => {
613                napi::bindgen_prelude::JsError::from(err).throw_into(env);
614                Ok(std::ptr::null_mut())
615              },
616            }
617          }
618        }
619      } else if is_return_self {
620        quote! { Ok(cb.this) }
621      } else {
622        quote! {
623          <#ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #ret)
624        }
625      }
626    } else {
627      quote! {
628        <() as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, ())
629      }
630    }
631  }
632
633  fn gen_fn_register(&self) -> TokenStream {
634    if self.parent.is_some() {
635      quote! {}
636    } else {
637      let name_str = self.name.to_string();
638      let js_name = format!("{}\0", &self.js_name);
639      let name_len = self.js_name.len();
640      let module_register_name = &self.register_name;
641      let intermediate_ident = get_intermediate_ident(&name_str);
642      let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref());
643      let cb_name = Ident::new(&format!("{}_js_function", name_str), Span::call_site());
644
645      quote! {
646        #[allow(non_snake_case)]
647        #[allow(clippy::all)]
648        unsafe fn #cb_name(env: napi::bindgen_prelude::sys::napi_env) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
649          let mut fn_ptr = std::ptr::null_mut();
650
651          napi::bindgen_prelude::check_status!(
652            napi::bindgen_prelude::sys::napi_create_function(
653              env,
654              #js_name.as_ptr().cast(),
655              #name_len,
656              Some(#intermediate_ident),
657              std::ptr::null_mut(),
658              &mut fn_ptr,
659            ),
660            "Failed to register function `{}`",
661            #name_str,
662          )?;
663          napi::bindgen_prelude::register_js_function(#js_name, #cb_name, Some(#intermediate_ident));
664          Ok(fn_ptr)
665        }
666
667        #[allow(clippy::all)]
668        #[allow(non_snake_case)]
669        #[cfg(all(not(test), not(target_family = "wasm")))]
670        #[napi::bindgen_prelude::ctor]
671        fn #module_register_name() {
672          napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name);
673        }
674
675        #[allow(clippy::all)]
676        #[allow(non_snake_case)]
677        #[cfg(all(not(test), target_family = "wasm"))]
678        #[no_mangle]
679        extern "C" fn #module_register_name() {
680          napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name);
681        }
682      }
683    }
684  }
685}
686
687struct ArgConversions {
688  pub args: Vec<TokenStream>,
689  pub arg_conversions: Vec<TokenStream>,
690  pub refs: Vec<TokenStream>,
691  pub mut_ref_spans: Vec<Span>,
692  pub unsafe_: bool,
693}
694
695#[derive(Debug, PartialEq, Eq)]
696enum NapiArgType {
697  Ref,
698  MutRef,
699  Value,
700}
701
702impl NapiArgType {
703  fn is_ref(&self) -> bool {
704    matches!(self, NapiArgType::Ref | NapiArgType::MutRef)
705  }
706}