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