Skip to main content

napi_derive_backend/codegen/
fn.rs

1use proc_macro2::{Ident, Span, TokenStream};
2use quote::ToTokens;
3use syn::{spanned::Spanned, Type, TypePath, TypeReference};
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
11#[cfg(feature = "tracing")]
12fn gen_tracing_debug(js_name: &str, parent_js_name: Option<&String>) -> TokenStream {
13  let full_name = if let Some(parent) = parent_js_name {
14    format!("{}::{}", parent, js_name)
15  } else {
16    js_name.to_string()
17  };
18  quote! {
19    napi::bindgen_prelude::tracing::debug!(target: "napi", "{}", #full_name);
20  }
21}
22
23#[cfg(not(feature = "tracing"))]
24fn gen_tracing_debug(_js_name: &str, _parent_js_name: Option<&String>) -> TokenStream {
25  quote! {}
26}
27
28impl TryToTokens for NapiFn {
29  fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> {
30    let name_str = self.name.to_string();
31    let intermediate_ident = get_intermediate_ident(&name_str);
32    let args_len = self.args.len();
33
34    let ArgConversions {
35      arg_conversions,
36      args: arg_names,
37      refs,
38      mut_ref_spans,
39      unsafe_,
40    } = self.gen_arg_conversions()?;
41    let attrs = &self.attrs;
42    let arg_ref_count = refs.len();
43    let receiver = self.gen_fn_receiver();
44    let receiver_ret_name = Ident::new("_ret", Span::call_site());
45    let ret = self.gen_fn_return(&receiver_ret_name)?;
46    let register = self.gen_fn_register();
47    let tracing_debug = gen_tracing_debug(&self.js_name, self.parent_js_name.as_ref());
48
49    if self.module_exports {
50      (quote! {
51        #(#attrs)*
52        #[doc(hidden)]
53        #[allow(non_snake_case)]
54        #[allow(clippy::all)]
55        unsafe extern "C" fn #intermediate_ident(
56          env: napi::bindgen_prelude::sys::napi_env,
57          _napi_module_exports_: napi::bindgen_prelude::sys::napi_value,
58        ) -> napi::Result<napi::bindgen_prelude::sys::napi_value> {
59          #tracing_debug
60          let __wrapped_env = napi::bindgen_prelude::Env::from(env);
61          #(#arg_conversions)*
62          let #receiver_ret_name = {
63            #receiver(#(#arg_names),*)
64          };
65          #ret
66        }
67
68        #register
69      })
70      .to_tokens(tokens);
71
72      return Ok(());
73    }
74
75    // The JS engine can't properly track mutability in an async context, so refuse to compile
76    // code that tries to use async and mutability together without `unsafe` mark.
77    if self.is_async && !mut_ref_spans.is_empty() && !unsafe_ {
78      return Diagnostic::from_vec(
79        mut_ref_spans
80          .into_iter()
81          .map(|s| Diagnostic::span_error(s, "mutable reference is unsafe with async"))
82          .collect(),
83      );
84    }
85    if Some(FnSelf::MutRef) == self.fn_self && self.is_async && !self.unsafe_ {
86      return Err(Diagnostic::span_error(
87        self.name.span(),
88        "&mut self in async napi methods should be marked as unsafe",
89      ));
90    }
91
92    if self.parent.is_some()
93      && matches!(self.kind, FnKind::Getter | FnKind::Setter)
94      && !self.is_async
95    {
96      let accessor_value_param = if self.kind == FnKind::Setter {
97        quote! { , value: napi::bindgen_prelude::sys::napi_value }
98      } else {
99        quote! {}
100      };
101      let accessor_args = if self.kind == FnKind::Setter && args_len > 0 {
102        quote! {
103          let mut __napi_accessor_args = [std::ptr::null_mut::<napi::bindgen_prelude::sys::napi_value__>(); #args_len];
104          __napi_accessor_args[0] = value;
105        }
106      } else {
107        quote! {
108          let __napi_accessor_args = [std::ptr::null_mut::<napi::bindgen_prelude::sys::napi_value__>(); #args_len];
109        }
110      };
111
112      let native_call = if self.within_async_runtime {
113        quote! {
114          napi::bindgen_prelude::within_runtime_if_available(move || {
115            let #receiver_ret_name = {
116              #receiver(#(#arg_names),*)
117            };
118            #ret
119          })
120        }
121      } else {
122        quote! {
123          let #receiver_ret_name = {
124            #receiver(#(#arg_names),*)
125          };
126          #ret
127        }
128      };
129
130      let function_call_inner = quote! {
131        #accessor_args
132        let mut cb = napi::bindgen_prelude::ClassAccessorCallbackInfo::<#args_len>::new(
133          env,
134          this,
135          __napi_accessor_args,
136        );
137        let __wrapped_env = napi::bindgen_prelude::Env::from(env);
138        #(#arg_conversions)*
139        #native_call
140      };
141
142      let function_call = if self.catch_unwind {
143        quote! {
144          {
145            std::panic::catch_unwind(|| { #function_call_inner })
146              .map_err(napi::bindgen_prelude::panic_to_error)
147              .and_then(|r| r)
148          }
149        }
150      } else {
151        quote! {
152          #function_call_inner
153        }
154      };
155
156      (quote! {
157        #(#attrs)*
158        #[doc(hidden)]
159        #[allow(non_snake_case)]
160        #[allow(clippy::all)]
161        unsafe fn #intermediate_ident(
162          env: napi::bindgen_prelude::sys::napi_env,
163          this: napi::bindgen_prelude::sys::napi_value
164          #accessor_value_param
165        ) -> napi::Result<napi::bindgen_prelude::sys::napi_value> {
166          #tracing_debug
167          unsafe {
168            #function_call
169          }
170        }
171
172        #register
173      })
174      .to_tokens(tokens);
175
176      return Ok(());
177    }
178
179    let build_ref_container = if self.is_async {
180      quote! {
181          struct NapiRefContainer([napi::sys::napi_ref; #arg_ref_count]);
182          impl NapiRefContainer {
183            fn drop(self, env: napi::sys::napi_env) {
184              for r in self.0.into_iter() {
185                assert_eq!(
186                  unsafe { napi::sys::napi_reference_unref(env, r, &mut 0) },
187                  napi::sys::Status::napi_ok,
188                  "failed to delete napi ref"
189                );
190                assert_eq!(
191                  unsafe { napi::sys::napi_delete_reference(env, r) },
192                  napi::sys::Status::napi_ok,
193                  "failed to delete napi ref"
194                );
195              }
196            }
197          }
198          unsafe impl Send for NapiRefContainer {}
199          unsafe impl Sync for NapiRefContainer {}
200          let _make_ref = |a: ::std::ptr::NonNull<napi::bindgen_prelude::sys::napi_value__>| {
201            let mut node_ref = ::std::mem::MaybeUninit::uninit();
202            napi::bindgen_prelude::check_status!(unsafe {
203                napi::bindgen_prelude::sys::napi_create_reference(env, a.as_ptr(), 1, node_ref.as_mut_ptr())
204              },
205              "failed to create napi ref"
206            )?;
207            Ok::<napi::sys::napi_ref, napi::Error>(unsafe { node_ref.assume_init() })
208          };
209          let mut _args_array = [::std::ptr::null_mut::<napi::bindgen_prelude::sys::napi_ref__>(); #arg_ref_count];
210          let mut _arg_write_index = 0;
211
212          #(#refs)*
213
214          #[cfg(debug_assertions)]
215          {
216            for a in &_args_array {
217              assert!(!a.is_null(), "failed to initialize napi ref");
218            }
219          }
220          let _args_ref = NapiRefContainer(_args_array);
221      }
222    } else {
223      quote! {}
224    };
225    let native_call = if !self.is_async {
226      if self.within_async_runtime {
227        quote! {
228          napi::bindgen_prelude::within_runtime_if_available(move || {
229            let #receiver_ret_name = {
230              #receiver(#(#arg_names),*)
231            };
232            #ret
233          })
234        }
235      } else {
236        quote! {
237          let #receiver_ret_name = {
238            #receiver(#(#arg_names),*)
239          };
240          #ret
241        }
242      }
243    } else {
244      let call = if self.is_ret_result {
245        quote! { #receiver(#(#arg_names),*).await }
246      } else {
247        let ret_type = if let Some(t) = &self.ret {
248          quote! { #t }
249        } else {
250          quote! { () }
251        };
252        quote! { Ok::<#ret_type, napi::Error>(#receiver(#(#arg_names),*).await) }
253      };
254      quote! {
255        napi::bindgen_prelude::execute_tokio_future_with_finalize_callback(env, async move { #call }, move |env, #receiver_ret_name| {
256          #ret
257        }, Some(Box::new(move |env| {
258          _args_ref.drop(env);
259        })))
260      }
261    };
262
263    // async factory only
264    let use_after_async = if self.is_async && self.parent.is_some() && self.fn_self.is_none() {
265      quote! { true }
266    } else {
267      quote! { false }
268    };
269
270    let function_call_inner = quote! {
271      napi::bindgen_prelude::CallbackInfo::<#args_len>::new(env, cb, None, #use_after_async).and_then(|#[allow(unused_mut)] mut cb| {
272          let __wrapped_env = napi::bindgen_prelude::Env::from(env);
273          #build_ref_container
274          #(#arg_conversions)*
275          #native_call
276        })
277    };
278
279    let function_call = if args_len == 0
280      && self.fn_self.is_none()
281      && self.kind != FnKind::Constructor
282      && self.kind != FnKind::Factory
283      && !self.is_async
284    {
285      quote! { #native_call }
286    } else if self.kind == FnKind::Constructor {
287      let return_from_factory = if self.catch_unwind {
288        quote! { return Ok(std::ptr::null_mut()); }
289      } else {
290        quote! { return std::ptr::null_mut(); }
291      };
292      quote! {
293        // constructor function is called from class `factory`
294        // so we should skip the original `constructor` logic
295        if napi::__private::___CALL_FROM_FACTORY.with(|inner| inner.get()) {
296            #return_from_factory
297        }
298        #function_call_inner
299      }
300    } else {
301      function_call_inner
302    };
303
304    let function_call = if self.catch_unwind {
305      quote! {
306        {
307          std::panic::catch_unwind(|| { #function_call })
308            .map_err(napi::bindgen_prelude::panic_to_error)
309            .and_then(|r| r)
310        }
311      }
312    } else {
313      quote! {
314        #function_call
315      }
316    };
317
318    (quote! {
319      #(#attrs)*
320      #[doc(hidden)]
321      #[allow(non_snake_case)]
322      #[allow(clippy::all)]
323      extern "C" fn #intermediate_ident(
324        env: napi::bindgen_prelude::sys::napi_env,
325        cb: napi::bindgen_prelude::sys::napi_callback_info
326      ) -> napi::bindgen_prelude::sys::napi_value {
327        #tracing_debug
328        unsafe {
329          #function_call.unwrap_or_else(|e| {
330            napi::bindgen_prelude::JsError::from(e).throw_into(env);
331            std::ptr::null_mut::<napi::bindgen_prelude::sys::napi_value__>()
332          })
333        }
334      }
335
336      #register
337    })
338    .to_tokens(tokens);
339
340    Ok(())
341  }
342}
343
344impl NapiFn {
345  fn gen_arg_conversions(&self) -> BindgenResult<ArgConversions> {
346    let mut arg_conversions = vec![];
347    let mut args = vec![];
348    let mut refs = vec![];
349    let mut mut_ref_spans = vec![];
350
351    // fetch this
352    if let Some(parent) = &self.parent {
353      match self.fn_self {
354        Some(FnSelf::Ref) => {
355          refs.push(make_ref(quote! { cb.this() }));
356          arg_conversions.push(quote! {
357            let this_ptr = cb.unwrap_raw::<#parent>()?;
358            let this: &#parent = Box::leak(Box::from_raw(this_ptr));
359          });
360        }
361        Some(FnSelf::MutRef) => {
362          refs.push(make_ref(quote! { cb.this() }));
363          arg_conversions.push(quote! {
364            let this_ptr = cb.unwrap_raw::<#parent>()?;
365            let this: &mut #parent = Box::leak(Box::from_raw(this_ptr));
366          });
367        }
368        _ => {}
369      };
370    }
371
372    let mut skipped_arg_count = 0;
373    for (i, arg) in self.args.iter().enumerate() {
374      let i = i - skipped_arg_count;
375      let ident = Ident::new(&format!("arg{i}"), Span::call_site());
376
377      match &arg.kind {
378        NapiFnArgKind::PatType(path) => {
379          if &path.ty.to_token_stream().to_string() == "Env" {
380            args.push(quote! { __wrapped_env });
381            skipped_arg_count += 1;
382          } else {
383            let is_in_class = self.parent.is_some();
384            // get `f64` in `foo: f64`
385            if let syn::Type::Path(path) = path.ty.as_ref() {
386              // get `Reference` in `napi::bindgen_prelude::Reference`
387              if let Some(p) = path.path.segments.last() {
388                if p.ident == "Reference" {
389                  if !is_in_class {
390                    bail_span!(p, "`Reference` is only allowed in class methods");
391                  }
392                  // get `FooStruct` in `Reference<FooStruct>`
393                  if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
394                    args: angle_bracketed_args,
395                    ..
396                  }) = &p.arguments
397                  {
398                    if let Some(syn::GenericArgument::Type(syn::Type::Path(path))) =
399                      angle_bracketed_args.first()
400                    {
401                      if let Some(p) = path.path.segments.first() {
402                        if p.ident == *self.parent.as_ref().unwrap() {
403                          args.push(quote! {
404                            napi::bindgen_prelude::Reference::from_value_ptr(this_ptr.cast(), env)?
405                          });
406                          skipped_arg_count += 1;
407                          continue;
408                        }
409                      }
410                    }
411                  }
412                } else if p.ident == "This" {
413                  // get `FooStruct` in `This<FooStruct>`
414                  if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
415                    args: angle_bracketed_args,
416                    ..
417                  }) = &p.arguments
418                  {
419                    if let Some(syn::GenericArgument::Type(generic_type)) =
420                      angle_bracketed_args.first()
421                    {
422                      if let syn::Type::Path(syn::TypePath {
423                        path: syn::Path { segments, .. },
424                        ..
425                      }) = generic_type
426                      {
427                        if let Some(syn::PathSegment { ident, .. }) = segments.first() {
428                          if let Some((primitive_type, _)) =
429                            crate::PRIMITIVE_TYPES.iter().find(|(p, _)| ident == *p)
430                          {
431                            bail_span!(
432                              ident,
433                              "This type must not be {} \nthis in JavaScript function must be `Object` type or `undefined`",
434                              primitive_type
435                            );
436                          }
437                          args.push(
438                            quote! {
439                              {
440                                <#ident as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb.this())?.into()
441                              }
442                            },
443                          );
444                          skipped_arg_count += 1;
445                          continue;
446                        }
447                      } else if let syn::Type::Reference(syn::TypeReference {
448                        elem,
449                        mutability,
450                        ..
451                      }) = generic_type
452                      {
453                        if let syn::Type::Path(syn::TypePath {
454                          path: syn::Path { segments, .. },
455                          ..
456                        }) = elem.as_ref()
457                        {
458                          if let Some(syn::PathSegment { ident, .. }) = segments.first() {
459                            refs.push(make_ref(quote! { cb.this() }));
460                            let token = if mutability.is_some() {
461                              mut_ref_spans.push(generic_type.span());
462                              quote! { <#ident as napi::bindgen_prelude::FromNapiMutRef>::from_napi_mut_ref(env, cb.this())?.into() }
463                            } else {
464                              quote! { <#ident as napi::bindgen_prelude::FromNapiRef>::from_napi_ref(env, cb.this())?.into() }
465                            };
466                            args.push(token);
467                            skipped_arg_count += 1;
468                            continue;
469                          }
470                        }
471                      }
472                    }
473                  }
474                  refs.push(make_ref(quote! { cb.this() }));
475                  args.push(quote! { <napi::bindgen_prelude::This as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb.this())? });
476                  skipped_arg_count += 1;
477                  continue;
478                }
479              }
480            }
481            let (arg_conversion, arg_type) = self.gen_ty_arg_conversion(&ident, i, path)?;
482            if NapiArgType::MutRef == arg_type {
483              mut_ref_spans.push(path.ty.span());
484            }
485            if arg_type.is_ref() {
486              refs.push(make_ref(quote! { cb.get_arg(#i) }));
487            }
488            if arg_type == NapiArgType::Env {
489              args.push(quote! { &__wrapped_env });
490              skipped_arg_count += 1;
491              continue;
492            }
493            arg_conversions.push(arg_conversion);
494            args.push(quote! { #ident });
495          }
496        }
497        NapiFnArgKind::Callback(cb) => {
498          arg_conversions.push(self.gen_cb_arg_conversion(&ident, i, cb)?);
499          args.push(quote! { #ident });
500        }
501      }
502    }
503
504    Ok(ArgConversions {
505      arg_conversions,
506      args,
507      refs,
508      mut_ref_spans,
509      unsafe_: self.unsafe_,
510    })
511  }
512
513  /// Returns a type conversion, and a boolean indicating whether this value needs to have a reference created to extend the lifetime
514  /// for async functions.
515  fn gen_ty_arg_conversion(
516    &self,
517    arg_name: &Ident,
518    index: usize,
519    path: &syn::PatType,
520  ) -> BindgenResult<(TokenStream, NapiArgType)> {
521    let mut ty = *path.ty.clone();
522    let type_check = if self.return_if_invalid {
523      quote! {
524        if let Ok(maybe_promise) = <#ty as napi::bindgen_prelude::ValidateNapiValue>::validate(env, cb.get_arg(#index)) {
525          if !maybe_promise.is_null() {
526            return Ok(maybe_promise);
527          }
528        } else {
529          return Ok(std::ptr::null_mut());
530        }
531      }
532    } else if self.strict {
533      quote! {
534        let maybe_promise = <#ty as napi::bindgen_prelude::ValidateNapiValue>::validate(env, cb.get_arg(#index))?;
535        if !maybe_promise.is_null() {
536          return Ok(maybe_promise);
537        }
538      }
539    } else {
540      quote! {}
541    };
542
543    let arg_conversion = if self.module_exports {
544      quote! { _napi_module_exports_ }
545    } else {
546      quote! { cb.get_arg(#index) }
547    };
548
549    match ty {
550      syn::Type::Reference(syn::TypeReference {
551        mutability: Some(_),
552        elem,
553        ..
554      }) => {
555        let q = quote! {
556          let #arg_name = {
557            #type_check
558            <#elem as napi::bindgen_prelude::FromNapiMutRef>::from_napi_mut_ref(env, cb.get_arg(#index))?
559          };
560        };
561        Ok((q, NapiArgType::MutRef))
562      }
563      syn::Type::Reference(syn::TypeReference {
564        mutability, elem, ..
565      }) => {
566        if let syn::Type::Slice(slice) = &*elem {
567          if let syn::Type::Path(ele) = &*slice.elem {
568            if let Some(syn::PathSegment { ident, .. }) = ele.path.segments.first() {
569              if TYPEDARRAY_SLICE_TYPES.contains_key(&&*ident.to_string()) {
570                let q = quote! {
571                  let #arg_name = {
572                    #type_check
573                    <&mut #elem as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb.get_arg(#index))?
574                  };
575                };
576                return Ok((q, NapiArgType::Ref));
577              }
578            }
579          }
580        }
581        let q = if mutability.is_some() {
582          quote! {
583            let #arg_name = {
584              #type_check
585              <#elem as napi::bindgen_prelude::FromNapiMutRef>::from_napi_mut_ref(env, cb.get_arg(#index))?
586            }
587          }
588        } else {
589          if let syn::Type::Path(ele) = &*elem {
590            if let Some(syn::PathSegment { ident, .. }) = ele.path.segments.last() {
591              if ident == "Env" {
592                return Ok((quote! {}, NapiArgType::Env));
593              } else if ident == "str" {
594                bail_span!(
595                  elem,
596                  "JavaScript String is primitive and cannot be passed by reference"
597                );
598              }
599            }
600          }
601          quote! {
602            let #arg_name = {
603              #type_check
604              <#elem as napi::bindgen_prelude::FromNapiRef>::from_napi_ref(env, cb.get_arg(#index))?
605            };
606          }
607        };
608        Ok((
609          q,
610          if mutability.is_some() {
611            NapiArgType::MutRef
612          } else {
613            NapiArgType::Ref
614          },
615        ))
616      }
617      _ => {
618        hidden_ty_lifetime(&mut ty)?;
619        let mut arg_type = NapiArgType::Value;
620        let mut is_array = false;
621        if let syn::Type::Path(path) = &ty {
622          // Detect cases where the type is `Vec<&S>`.
623          // For example, in `async fn foo(v: Vec<&S>) {}`, we need to handle `v` as a reference.
624          if let Some(syn::PathSegment { ident, arguments }) = path.path.segments.first() {
625            // Check if the type is a `Vec`.
626            if ident == "Vec" {
627              is_array = true;
628              if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
629                args: angle_bracketed_args,
630                ..
631              }) = &arguments
632              {
633                // Check if the generic argument of `Vec` is a reference type (e.g., `&S`).
634                if let Some(syn::GenericArgument::Type(syn::Type::Reference(
635                  syn::TypeReference { .. },
636                ))) = angle_bracketed_args.first()
637                {
638                  // If the type is `Vec<&S>`, set the argument type to `Ref`.
639                  arg_type = NapiArgType::Ref;
640                }
641              }
642            }
643          }
644        }
645        // Array::validate only validates by the `Array.isArray`
646        // For the elements of the Array, we need to return rather than throw if they are invalid when `return_if_invalid` is true
647        let from_napi_value = if is_array && self.return_if_invalid {
648          quote! {
649            match <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, #arg_conversion) {
650              Ok(value) => value,
651              Err(err) => {
652                // InvalidArg, ObjectExpected, StringExpected ...
653                if err.status < napi::bindgen_prelude::Status::GenericFailure {
654                  return Ok(std::ptr::null_mut());
655                } else {
656                  return Err(err);
657                }
658              }
659            }
660          }
661        } else {
662          quote! {
663            <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, #arg_conversion)?
664          }
665        };
666        let q = quote! {
667          let #arg_name = {
668            #type_check
669            #from_napi_value
670          };
671        };
672        Ok((q, arg_type))
673      }
674    }
675  }
676
677  fn gen_cb_arg_conversion(
678    &self,
679    arg_name: &Ident,
680    index: usize,
681    cb: &CallbackArg,
682  ) -> BindgenResult<TokenStream> {
683    let mut inputs = vec![];
684    let mut arg_conversions = vec![];
685
686    for (i, ty) in cb.args.iter().enumerate() {
687      let cb_arg_ident = Ident::new(&format!("callback_arg_{i}"), Span::call_site());
688      inputs.push(quote! { #cb_arg_ident: #ty });
689      let mut maybe_has_lifetime_ty = ty.clone();
690      hidden_ty_lifetime(&mut maybe_has_lifetime_ty)?;
691      arg_conversions.push(
692        quote! { <#maybe_has_lifetime_ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #cb_arg_ident)? },
693      );
694    }
695
696    let ret = match &cb.ret {
697      Some(ty) => {
698        quote! {
699          let ret = <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, ret_ptr)?;
700
701          Ok(ret)
702        }
703      }
704      None => quote! { Ok(()) },
705    };
706
707    Ok(quote! {
708      napi::bindgen_prelude::assert_type_of!(env, cb.get_arg(#index), napi::bindgen_prelude::ValueType::Function)?;
709      let #arg_name = |#(#inputs),*| {
710        let args = vec![
711          #(#arg_conversions),*
712        ];
713
714        let mut ret_ptr = std::ptr::null_mut();
715
716        napi::bindgen_prelude::check_pending_exception!(
717          env,
718          napi::bindgen_prelude::sys::napi_call_function(
719            env,
720            cb.this(),
721            cb.get_arg(#index),
722            args.len(),
723            args.as_ptr(),
724            &mut ret_ptr
725          )
726        )?;
727
728        #ret
729      };
730    })
731  }
732
733  fn gen_fn_receiver(&self) -> TokenStream {
734    let name = &self.name;
735
736    match self.fn_self {
737      Some(FnSelf::Value) => {
738        // impossible, panic! in parser
739        unreachable!();
740      }
741      Some(FnSelf::Ref) | Some(FnSelf::MutRef) => quote! { this.#name },
742      None => match &self.parent {
743        Some(class) => quote! { #class::#name },
744        None => quote! { #name },
745      },
746    }
747  }
748
749  fn gen_fn_return(&self, ret: &Ident) -> BindgenResult<TokenStream> {
750    let js_name = &self.js_name;
751
752    if let Some(ty) = &self.ret {
753      let ty_string = ty.into_token_stream().to_string();
754      let is_return_self = ty_string == "& Self" || ty_string == "&mut Self";
755      if self.kind == FnKind::Constructor {
756        let parent = self
757          .parent
758          .as_ref()
759          .expect("Parent must exist for constructor");
760        if self.is_ret_result {
761          if self.parent_is_generator {
762            Ok(quote! { cb.construct_generator::<false, _>(#js_name, #ret?) })
763          } else if self.parent_is_async_generator {
764            Ok(quote! { cb.construct_async_generator::<false, _>(#js_name, #ret?) })
765          } else {
766            Ok(quote! {
767              match #ret {
768                Ok(value) => {
769                  cb.construct::<false, _>(#js_name, value)
770                }
771                Err(err) => {
772                  napi::bindgen_prelude::JsError::from(err).throw_into(env);
773                  Ok(std::ptr::null_mut())
774                }
775              }
776            })
777          }
778        } else if self.parent_is_generator {
779          Ok(quote! { cb.construct_generator::<false, #parent>(#js_name, #ret) })
780        } else if self.parent_is_async_generator {
781          Ok(quote! { cb.construct_async_generator::<false, #parent>(#js_name, #ret) })
782        } else {
783          Ok(quote! { cb.construct::<false, #parent>(#js_name, #ret) })
784        }
785      } else if self.kind == FnKind::Factory {
786        if self.is_ret_result {
787          if self.parent_is_generator {
788            Ok(quote! { cb.generator_factory(#js_name, #ret?) })
789          } else if self.parent_is_async_generator {
790            Ok(quote! { cb.async_generator_factory(#js_name, #ret?) })
791          } else if self.is_async {
792            Ok(quote! { cb.factory(#js_name, #ret) })
793          } else {
794            Ok(quote! {
795              match #ret {
796                Ok(value) => {
797                  cb.factory(#js_name, value)
798                }
799                Err(err) => {
800                  napi::bindgen_prelude::JsError::from(err).throw_into(env);
801                  Ok(std::ptr::null_mut())
802                }
803              }
804            })
805          }
806        } else if self.parent_is_generator {
807          Ok(quote! { cb.generator_factory(#js_name, #ret) })
808        } else if self.parent_is_async_generator {
809          Ok(quote! { cb.async_generator_factory(#js_name, #ret) })
810        } else {
811          Ok(quote! { cb.factory(#js_name, #ret) })
812        }
813      } else if self.is_ret_result {
814        if self.is_async {
815          Ok(quote! {
816            <#ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #ret)
817          })
818        } else if is_return_self {
819          Ok(quote! { #ret.map(|_| cb.this()) })
820        } else {
821          Ok(quote! {
822            match #ret {
823              Ok(value) => napi::bindgen_prelude::ToNapiValue::to_napi_value(env, value),
824              Err(err) => {
825                napi::bindgen_prelude::JsError::from(err).throw_into(env);
826                Ok(std::ptr::null_mut())
827              },
828            }
829          })
830        }
831      } else if is_return_self {
832        Ok(quote! { Ok(cb.this()) })
833      } else {
834        let mut return_ty = ty.clone();
835        hidden_ty_lifetime(&mut return_ty)?;
836        Ok(quote! {
837          <#return_ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #ret)
838        })
839      }
840    } else {
841      Ok(quote! {
842        <() as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, ())
843      })
844    }
845  }
846
847  fn gen_fn_register(&self) -> TokenStream {
848    if self.parent.is_some() || cfg!(test) {
849      quote! {}
850    } else {
851      let name_str = self.name.to_string();
852      let js_name = format!("{}\0", &self.js_name);
853      let name_len = self.js_name.len();
854      let module_register_name = &self.register_name;
855      let intermediate_ident = get_intermediate_ident(&name_str);
856      let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref());
857      let cb_name = Ident::new(
858        &format!("_napi_rs_internal_register_{name_str}"),
859        Span::call_site(),
860      );
861
862      if self.module_exports {
863        return quote! {
864          #[doc(hidden)]
865          #[allow(non_snake_case)]
866          #[allow(clippy::all)]
867          unsafe fn #cb_name(env: napi::bindgen_prelude::sys::napi_env, exports: napi::bindgen_prelude::sys::napi_value) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
868            #intermediate_ident(env, exports)?;
869            Ok(exports)
870          }
871
872          #[cfg(all(not(test), not(target_family = "wasm")))]
873          napi::ctor::declarative::ctor! {
874            #[doc(hidden)]
875            #[allow(clippy::all)]
876            #[allow(non_snake_case)]
877            #[ctor(unsafe)]
878            fn #module_register_name() {
879              napi::bindgen_prelude::register_module_export_hook(#cb_name);
880            }
881          }
882
883          #[allow(clippy::all)]
884          #[allow(non_snake_case)]
885          #[cfg(all(not(test), target_family = "wasm"))]
886          #[no_mangle]
887          extern "C" fn #module_register_name() {
888            napi::bindgen_prelude::register_module_export_hook(#cb_name);
889          }
890        };
891      }
892
893      let register_module_export_tokens = if self.no_export {
894        quote! {}
895      } else {
896        quote! {
897          #[cfg(all(not(test), not(target_family = "wasm")))]
898          napi::ctor::declarative::ctor! {
899            #[doc(hidden)]
900            #[allow(clippy::all)]
901            #[allow(non_snake_case)]
902            #[ctor(unsafe)]
903            fn #module_register_name() {
904              napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name);
905            }
906          }
907
908          #[doc(hidden)]
909          #[allow(clippy::all)]
910          #[allow(non_snake_case)]
911          #[cfg(all(not(test), target_family = "wasm"))]
912          #[no_mangle]
913          extern "C" fn #module_register_name() {
914            napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name);
915          }
916        }
917      };
918
919      quote! {
920        #[doc(hidden)]
921        #[allow(non_snake_case)]
922        #[allow(clippy::all)]
923        unsafe fn #cb_name(env: napi::bindgen_prelude::sys::napi_env) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
924          let mut fn_ptr = std::ptr::null_mut();
925
926          napi::bindgen_prelude::check_status!(
927            napi::bindgen_prelude::sys::napi_create_function(
928              env,
929              #js_name.as_ptr().cast(),
930              #name_len as isize,
931              Some(#intermediate_ident),
932              std::ptr::null_mut(),
933              &mut fn_ptr,
934            ),
935            "Failed to register function `{}`",
936            #name_str,
937          )?;
938          Ok(fn_ptr)
939        }
940
941        #register_module_export_tokens
942      }
943    }
944  }
945}
946
947fn hidden_ty_lifetime(ty: &mut syn::Type) -> BindgenResult<()> {
948  match ty {
949    Type::Path(TypePath {
950      path: syn::Path { segments, .. },
951      ..
952    }) => {
953      if let Some(syn::PathSegment {
954        arguments:
955          syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { args, .. }),
956        ..
957      }) = segments.last_mut()
958      {
959        let mut has_lifetime = false;
960        if let Some(syn::GenericArgument::Lifetime(lt)) = args.first_mut() {
961          *lt = syn::Lifetime::new("'_", Span::call_site());
962          has_lifetime = true;
963        }
964        for arg in args.iter_mut().skip(if has_lifetime { 1 } else { 0 }) {
965          if let syn::GenericArgument::Type(ty) = arg {
966            hidden_ty_lifetime(ty)?;
967          }
968        }
969      }
970    }
971    Type::Reference(TypeReference {
972      lifetime: Some(lt), ..
973    }) => {
974      *lt = syn::Lifetime::new("'_", Span::call_site());
975    }
976    _ => {}
977  }
978  Ok(())
979}
980
981fn make_ref(input: TokenStream) -> TokenStream {
982  quote! {
983    _args_array[_arg_write_index] = _make_ref(
984      ::std::ptr::NonNull::new(#input)
985        .ok_or_else(|| napi::Error::new(napi::Status::InvalidArg, "referenced ptr is null".to_owned()))?
986    )?;
987    _arg_write_index += 1;
988  }
989}
990
991struct ArgConversions {
992  pub args: Vec<TokenStream>,
993  pub arg_conversions: Vec<TokenStream>,
994  pub refs: Vec<TokenStream>,
995  pub mut_ref_spans: Vec<Span>,
996  pub unsafe_: bool,
997}
998
999#[derive(Debug, PartialEq, Eq)]
1000enum NapiArgType {
1001  Ref,
1002  MutRef,
1003  Value,
1004  Env,
1005}
1006
1007impl NapiArgType {
1008  fn is_ref(&self) -> bool {
1009    matches!(self, NapiArgType::Ref | NapiArgType::MutRef)
1010  }
1011}