napi_derive_backend/codegen/
fn.rs

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