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
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        let mut is_array = false;
524        if let syn::Type::Path(path) = &ty {
525          // Detect cases where the type is `Vec<&S>`.
526          // For example, in `async fn foo(v: Vec<&S>) {}`, we need to handle `v` as a reference.
527          if let Some(syn::PathSegment { ident, arguments }) = path.path.segments.first() {
528            // Check if the type is a `Vec`.
529            if ident == "Vec" {
530              is_array = true;
531              if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
532                args: angle_bracketed_args,
533                ..
534              }) = &arguments
535              {
536                // Check if the generic argument of `Vec` is a reference type (e.g., `&S`).
537                if let Some(syn::GenericArgument::Type(syn::Type::Reference(
538                  syn::TypeReference { .. },
539                ))) = angle_bracketed_args.first()
540                {
541                  // If the type is `Vec<&S>`, set the argument type to `Ref`.
542                  arg_type = NapiArgType::Ref;
543                }
544              }
545            }
546          }
547        }
548        // Array::validate only validates by the `Array.isArray`
549        // For the elements of the Array, we need to return rather than throw if they are invalid when `return_if_invalid` is true
550        let from_napi_value = if is_array && self.return_if_invalid {
551          quote! {
552            match <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, #arg_conversion) {
553              Ok(value) => value,
554              Err(err) => {
555                // InvalidArg, ObjectExpected, StringExpected ...
556                if err.status < napi::bindgen_prelude::Status::GenericFailure {
557                  return Ok(std::ptr::null_mut());
558                } else {
559                  return Err(err);
560                }
561              }
562            }
563          }
564        } else {
565          quote! {
566            <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, #arg_conversion)?
567          }
568        };
569        let q = quote! {
570          let #arg_name = {
571            #type_check
572            #from_napi_value
573          };
574        };
575        Ok((q, arg_type))
576      }
577    }
578  }
579
580  fn gen_cb_arg_conversion(
581    &self,
582    arg_name: &Ident,
583    index: usize,
584    cb: &CallbackArg,
585  ) -> BindgenResult<TokenStream> {
586    let mut inputs = vec![];
587    let mut arg_conversions = vec![];
588
589    for (i, ty) in cb.args.iter().enumerate() {
590      let cb_arg_ident = Ident::new(&format!("callback_arg_{i}"), Span::call_site());
591      inputs.push(quote! { #cb_arg_ident: #ty });
592      let mut maybe_has_lifetime_ty = ty.clone();
593      hidden_ty_lifetime(&mut maybe_has_lifetime_ty)?;
594      arg_conversions.push(
595        quote! { <#maybe_has_lifetime_ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #cb_arg_ident)? },
596      );
597    }
598
599    let ret = match &cb.ret {
600      Some(ty) => {
601        quote! {
602          let ret = <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, ret_ptr)?;
603
604          Ok(ret)
605        }
606      }
607      None => quote! { Ok(()) },
608    };
609
610    Ok(quote! {
611      napi::bindgen_prelude::assert_type_of!(env, cb.get_arg(#index), napi::bindgen_prelude::ValueType::Function)?;
612      let #arg_name = |#(#inputs),*| {
613        let args = vec![
614          #(#arg_conversions),*
615        ];
616
617        let mut ret_ptr = std::ptr::null_mut();
618
619        napi::bindgen_prelude::check_pending_exception!(
620          env,
621          napi::bindgen_prelude::sys::napi_call_function(
622            env,
623            cb.this(),
624            cb.get_arg(#index),
625            args.len(),
626            args.as_ptr(),
627            &mut ret_ptr
628          )
629        )?;
630
631        #ret
632      };
633    })
634  }
635
636  fn gen_fn_receiver(&self) -> TokenStream {
637    let name = &self.name;
638
639    match self.fn_self {
640      Some(FnSelf::Value) => {
641        // impossible, panic! in parser
642        unreachable!();
643      }
644      Some(FnSelf::Ref) | Some(FnSelf::MutRef) => quote! { this.#name },
645      None => match &self.parent {
646        Some(class) => quote! { #class::#name },
647        None => quote! { #name },
648      },
649    }
650  }
651
652  fn gen_fn_return(&self, ret: &Ident) -> BindgenResult<TokenStream> {
653    let js_name = &self.js_name;
654
655    if let Some(ty) = &self.ret {
656      let ty_string = ty.into_token_stream().to_string();
657      let is_return_self = ty_string == "& Self" || ty_string == "&mut Self";
658      if self.kind == FnKind::Constructor {
659        let parent = self
660          .parent
661          .as_ref()
662          .expect("Parent must exist for constructor");
663        if self.is_ret_result {
664          if self.parent_is_generator {
665            Ok(quote! { cb.construct_generator::<false, _>(#js_name, #ret?) })
666          } else {
667            Ok(quote! {
668              match #ret {
669                Ok(value) => {
670                  cb.construct::<false, _>(#js_name, value)
671                }
672                Err(err) => {
673                  napi::bindgen_prelude::JsError::from(err).throw_into(env);
674                  Ok(std::ptr::null_mut())
675                }
676              }
677            })
678          }
679        } else if self.parent_is_generator {
680          Ok(quote! { cb.construct_generator::<false, #parent>(#js_name, #ret) })
681        } else {
682          Ok(quote! { cb.construct::<false, #parent>(#js_name, #ret) })
683        }
684      } else if self.kind == FnKind::Factory {
685        if self.is_ret_result {
686          if self.parent_is_generator {
687            Ok(quote! { cb.generator_factory(#js_name, #ret?) })
688          } else if self.is_async {
689            Ok(quote! { cb.factory(#js_name, #ret) })
690          } else {
691            Ok(quote! {
692              match #ret {
693                Ok(value) => {
694                  cb.factory(#js_name, value)
695                }
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 self.parent_is_generator {
704          Ok(quote! { cb.generator_factory(#js_name, #ret) })
705        } else {
706          Ok(quote! { cb.factory(#js_name, #ret) })
707        }
708      } else if self.is_ret_result {
709        if self.is_async {
710          Ok(quote! {
711            <#ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #ret)
712          })
713        } else if is_return_self {
714          Ok(quote! { #ret.map(|_| cb.this) })
715        } else {
716          Ok(quote! {
717            match #ret {
718              Ok(value) => napi::bindgen_prelude::ToNapiValue::to_napi_value(env, value),
719              Err(err) => {
720                napi::bindgen_prelude::JsError::from(err).throw_into(env);
721                Ok(std::ptr::null_mut())
722              },
723            }
724          })
725        }
726      } else if is_return_self {
727        Ok(quote! { Ok(cb.this) })
728      } else {
729        let mut return_ty = ty.clone();
730        hidden_ty_lifetime(&mut return_ty)?;
731        Ok(quote! {
732          <#return_ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #ret)
733        })
734      }
735    } else {
736      Ok(quote! {
737        <() as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, ())
738      })
739    }
740  }
741
742  fn gen_fn_register(&self) -> TokenStream {
743    if self.parent.is_some() || cfg!(test) {
744      quote! {}
745    } else {
746      let name_str = self.name.to_string();
747      let js_name = format!("{}\0", &self.js_name);
748      let name_len = self.js_name.len();
749      let module_register_name = &self.register_name;
750      let intermediate_ident = get_intermediate_ident(&name_str);
751      let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref());
752      let cb_name = Ident::new(
753        &format!("_napi_rs_internal_register_{name_str}"),
754        Span::call_site(),
755      );
756
757      if self.module_exports {
758        return quote! {
759          #[doc(hidden)]
760          #[allow(non_snake_case)]
761          #[allow(clippy::all)]
762          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> {
763            #intermediate_ident(env, exports)?;
764            Ok(exports)
765          }
766
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_hook(#cb_name);
774          }
775
776          #[allow(clippy::all)]
777          #[allow(non_snake_case)]
778          #[cfg(all(not(test), target_family = "wasm"))]
779          #[no_mangle]
780          extern "C" fn #module_register_name() {
781            napi::bindgen_prelude::register_module_export_hook(#cb_name);
782          }
783        };
784      }
785
786      let register_module_export_tokens = if self.no_export {
787        quote! {}
788      } else {
789        quote! {
790          #[doc(hidden)]
791          #[allow(clippy::all)]
792          #[allow(non_snake_case)]
793          #[cfg(all(not(test), not(target_family = "wasm")))]
794          #[napi::ctor::ctor(crate_path=::napi::ctor)]
795          fn #module_register_name() {
796            napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name);
797          }
798
799          #[doc(hidden)]
800          #[allow(clippy::all)]
801          #[allow(non_snake_case)]
802          #[cfg(all(not(test), target_family = "wasm"))]
803          #[no_mangle]
804          extern "C" fn #module_register_name() {
805            napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name);
806          }
807        }
808      };
809
810      quote! {
811        #[doc(hidden)]
812        #[allow(non_snake_case)]
813        #[allow(clippy::all)]
814        unsafe fn #cb_name(env: napi::bindgen_prelude::sys::napi_env) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
815          let mut fn_ptr = std::ptr::null_mut();
816
817          napi::bindgen_prelude::check_status!(
818            napi::bindgen_prelude::sys::napi_create_function(
819              env,
820              #js_name.as_ptr().cast(),
821              #name_len as isize,
822              Some(#intermediate_ident),
823              std::ptr::null_mut(),
824              &mut fn_ptr,
825            ),
826            "Failed to register function `{}`",
827            #name_str,
828          )?;
829          Ok(fn_ptr)
830        }
831
832        #register_module_export_tokens
833      }
834    }
835  }
836}
837
838fn hidden_ty_lifetime(ty: &mut syn::Type) -> BindgenResult<()> {
839  match ty {
840    Type::Path(TypePath {
841      path: syn::Path { segments, .. },
842      ..
843    }) => {
844      if let Some(syn::PathSegment {
845        arguments:
846          syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { args, .. }),
847        ..
848      }) = segments.last_mut()
849      {
850        let mut has_lifetime = false;
851        if let Some(syn::GenericArgument::Lifetime(lt)) = args.first_mut() {
852          *lt = syn::Lifetime::new("'_", Span::call_site());
853          has_lifetime = true;
854        }
855        for arg in args.iter_mut().skip(if has_lifetime { 1 } else { 0 }) {
856          if let syn::GenericArgument::Type(ty) = arg {
857            hidden_ty_lifetime(ty)?;
858          }
859        }
860      }
861    }
862    Type::Reference(TypeReference {
863      lifetime: Some(lt), ..
864    }) => {
865      *lt = syn::Lifetime::new("'_", Span::call_site());
866    }
867    _ => {}
868  }
869  Ok(())
870}
871
872fn make_ref(input: TokenStream) -> TokenStream {
873  quote! {
874    _args_array[_arg_write_index] = _make_ref(
875      ::std::ptr::NonNull::new(#input)
876        .ok_or_else(|| napi::Error::new(napi::Status::InvalidArg, "referenced ptr is null".to_owned()))?
877    )?;
878    _arg_write_index += 1;
879  }
880}
881
882struct ArgConversions {
883  pub args: Vec<TokenStream>,
884  pub arg_conversions: Vec<TokenStream>,
885  pub refs: Vec<TokenStream>,
886  pub mut_ref_spans: Vec<Span>,
887  pub unsafe_: bool,
888}
889
890#[derive(Debug, PartialEq, Eq)]
891enum NapiArgType {
892  Ref,
893  MutRef,
894  Value,
895  Env,
896}
897
898impl NapiArgType {
899  fn is_ref(&self) -> bool {
900    matches!(self, NapiArgType::Ref | NapiArgType::MutRef)
901  }
902}