Skip to main content

napi_derive_backend/codegen/
fn.rs

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