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