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