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