Skip to main content

napi_derive_backend/codegen/
struct.rs

1use std::collections::HashMap;
2use std::sync::atomic::{AtomicU32, Ordering};
3
4use proc_macro2::{Ident, Literal, Span, TokenStream};
5use quote::ToTokens;
6
7use crate::util::to_case;
8
9use crate::{
10  codegen::{get_intermediate_ident, js_mod_to_token_stream},
11  BindgenResult, FnKind, NapiImpl, NapiStruct, NapiStructKind, TryToTokens,
12};
13use crate::{NapiArray, NapiClass, NapiObject, NapiStructuredEnum, NapiTransparent};
14
15static NAPI_IMPL_ID: AtomicU32 = AtomicU32::new(0);
16
17const STRUCT_FIELD_SPECIAL_CASE: &[&str] = &["Option", "Result"];
18
19#[cfg(feature = "tracing")]
20fn gen_tracing_debug(class_name: &str, method_name: &str) -> TokenStream {
21  let full_name = format!("{}::{}", class_name, method_name);
22  quote! {
23    napi::bindgen_prelude::tracing::debug!(target: "napi", "{}", #full_name);
24  }
25}
26
27#[cfg(not(feature = "tracing"))]
28fn gen_tracing_debug(_class_name: &str, _method_name: &str) -> TokenStream {
29  quote! {}
30}
31
32// Generate trait implementations for given Struct.
33fn gen_napi_value_map_impl(
34  name: &Ident,
35  to_napi_val_impl: TokenStream,
36  has_lifetime: bool,
37) -> TokenStream {
38  let name_str = name.to_string();
39  let name = if has_lifetime {
40    quote! { #name<'_> }
41  } else {
42    quote! { #name }
43  };
44  let js_name_str = format!("{name_str}\0");
45  let validate = quote! {
46    unsafe fn validate(env: napi::sys::napi_env, napi_val: napi::sys::napi_value) -> napi::Result<napi::sys::napi_value> {
47      if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) {
48        let mut ctor = std::ptr::null_mut();
49        napi::check_status!(
50          napi::sys::napi_get_reference_value(env, ctor_ref, &mut ctor),
51          "Failed to get constructor reference of class `{}`",
52          #name_str
53        )?;
54        let mut is_instance_of = false;
55        napi::check_status!(
56          napi::sys::napi_instanceof(env, napi_val, ctor, &mut is_instance_of),
57          "Failed to get external value of class `{}`",
58          #name_str
59        )?;
60        if is_instance_of {
61          Ok(std::ptr::null_mut())
62        } else {
63          Err(napi::Error::new(
64            napi::Status::InvalidArg,
65            format!("Value is not instanceof class `{}`", #name_str)
66          ))
67        }
68      } else {
69        Err(napi::Error::new(
70          napi::Status::InvalidArg,
71          format!("Failed to get constructor of class `{}`", #name_str)
72        ))
73      }
74    }
75  };
76  quote! {
77    #[automatically_derived]
78    impl napi::bindgen_prelude::TypeName for #name {
79      fn type_name() -> &'static str {
80        #name_str
81      }
82
83      fn value_type() -> napi::ValueType {
84        napi::ValueType::Function
85      }
86    }
87
88    #[automatically_derived]
89    impl napi::bindgen_prelude::TypeName for &#name {
90      fn type_name() -> &'static str {
91        #name_str
92      }
93
94      fn value_type() -> napi::ValueType {
95        napi::ValueType::Object
96      }
97    }
98
99    #[automatically_derived]
100    impl napi::bindgen_prelude::TypeName for &mut #name {
101      fn type_name() -> &'static str {
102        #name_str
103      }
104
105      fn value_type() -> napi::ValueType {
106        napi::ValueType::Object
107      }
108    }
109
110    #to_napi_val_impl
111
112    #[automatically_derived]
113    impl napi::bindgen_prelude::FromNapiRef for #name {
114      unsafe fn from_napi_ref(
115        env: napi::bindgen_prelude::sys::napi_env,
116        napi_val: napi::bindgen_prelude::sys::napi_value
117      ) -> napi::bindgen_prelude::Result<&'static Self> {
118        let mut wrapped_val: *mut std::ffi::c_void = std::ptr::null_mut();
119
120        napi::bindgen_prelude::check_status!(
121          napi::bindgen_prelude::sys::napi_unwrap(env, napi_val, &mut wrapped_val),
122          "Failed to recover `{}` type from napi value",
123          #name_str,
124        )?;
125
126        Ok(&*(wrapped_val as *const #name))
127      }
128    }
129
130    #[automatically_derived]
131    impl napi::bindgen_prelude::FromNapiMutRef for #name {
132      unsafe fn from_napi_mut_ref(
133        env: napi::bindgen_prelude::sys::napi_env,
134        napi_val: napi::bindgen_prelude::sys::napi_value
135      ) -> napi::bindgen_prelude::Result<&'static mut Self> {
136        let mut wrapped_val: *mut std::ffi::c_void = std::ptr::null_mut();
137
138        napi::bindgen_prelude::check_status!(
139          napi::bindgen_prelude::sys::napi_unwrap(env, napi_val, &mut wrapped_val),
140          "Failed to recover `{}` type from napi value",
141          #name_str,
142        )?;
143
144        Ok(&mut *(wrapped_val as *mut #name))
145      }
146    }
147
148    #[automatically_derived]
149    impl napi::bindgen_prelude::ValidateNapiValue for &#name {
150      #validate
151    }
152
153    #[automatically_derived]
154    impl napi::bindgen_prelude::ValidateNapiValue for &mut #name {
155      #validate
156    }
157  }
158}
159
160impl TryToTokens for NapiStruct {
161  fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> {
162    let napi_value_map_impl = self.gen_napi_value_map_impl();
163
164    let class_helper_mod = match &self.kind {
165      NapiStructKind::Class(class) => self.gen_helper_mod(class),
166      _ => quote! {},
167    };
168
169    (quote! {
170      #napi_value_map_impl
171      #class_helper_mod
172    })
173    .to_tokens(tokens);
174
175    Ok(())
176  }
177}
178
179impl NapiStruct {
180  fn gen_helper_mod(&self, class: &NapiClass) -> TokenStream {
181    let mod_name = Ident::new(&format!("__napi_helper__{}", self.name), Span::call_site());
182
183    let ctor = if class.ctor {
184      self.gen_default_ctor(class)
185    } else {
186      quote! {}
187    };
188
189    let mut getters_setters = self.gen_default_getters_setters(class);
190    getters_setters.sort_by(|a, b| a.0.cmp(&b.0));
191    let register = self.gen_register(class);
192
193    let getters_setters_token = getters_setters.into_iter().map(|(_, token)| token);
194
195    quote! {
196      #[allow(clippy::all)]
197      #[allow(non_snake_case)]
198      mod #mod_name {
199        use std::ptr;
200        use super::*;
201
202        #ctor
203        #(#getters_setters_token)*
204        #register
205      }
206    }
207  }
208
209  fn gen_default_ctor(&self, class: &NapiClass) -> TokenStream {
210    let name = &self.name;
211    let js_name_str = &self.js_name;
212    let fields_len = class.fields.len();
213    let mut fields = vec![];
214
215    for (i, field) in class.fields.iter().enumerate() {
216      let ty = &field.ty;
217      match &field.name {
218        syn::Member::Named(ident) => fields
219          .push(quote! { #ident: <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb.get_arg(#i))? }),
220        syn::Member::Unnamed(_) => {
221          fields.push(quote! { <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb.get_arg(#i))? });
222        }
223      }
224    }
225
226    let construct = if class.is_tuple {
227      quote! { #name (#(#fields),*) }
228    } else {
229      quote! { #name {#(#fields),*} }
230    };
231
232    let is_empty_struct_hint = fields_len == 0;
233
234    let constructor = if class.implement_iterator {
235      quote! { unsafe { cb.construct_generator::<#is_empty_struct_hint, #name>(#js_name_str, #construct) } }
236    } else {
237      quote! { unsafe { cb.construct::<#is_empty_struct_hint, #name>(#js_name_str, #construct) } }
238    };
239
240    let tracing_debug = gen_tracing_debug(js_name_str, "constructor");
241
242    quote! {
243      extern "C" fn constructor(
244        env: napi::bindgen_prelude::sys::napi_env,
245        cb: napi::bindgen_prelude::sys::napi_callback_info
246      ) -> napi::bindgen_prelude::sys::napi_value {
247        #tracing_debug
248        napi::bindgen_prelude::CallbackInfo::<#fields_len>::new(env, cb, None, false)
249          .and_then(|cb| #constructor)
250          .unwrap_or_else(|e| {
251            unsafe { napi::bindgen_prelude::JsError::from(e).throw_into(env) };
252            std::ptr::null_mut::<napi::bindgen_prelude::sys::napi_value__>()
253          })
254      }
255    }
256  }
257
258  fn gen_napi_value_map_impl(&self) -> TokenStream {
259    match &self.kind {
260      NapiStructKind::Array(array) => self.gen_napi_value_array_impl(array),
261      NapiStructKind::Transparent(transparent) => self.gen_napi_value_transparent_impl(transparent),
262      NapiStructKind::Class(class) if !class.ctor => gen_napi_value_map_impl(
263        &self.name,
264        self.gen_to_napi_value_ctor_impl_for_non_default_constructor_struct(class),
265        self.has_lifetime,
266      ),
267      NapiStructKind::Class(class) => gen_napi_value_map_impl(
268        &self.name,
269        self.gen_to_napi_value_ctor_impl(class),
270        self.has_lifetime,
271      ),
272      NapiStructKind::Object(obj) => self.gen_to_napi_value_obj_impl(obj),
273      NapiStructKind::StructuredEnum(structured_enum) => {
274        self.gen_to_napi_value_structured_enum_impl(structured_enum)
275      }
276    }
277  }
278
279  fn gen_to_napi_value_ctor_impl_for_non_default_constructor_struct(
280    &self,
281    class: &NapiClass,
282  ) -> TokenStream {
283    let name = &self.name;
284    let js_name_raw = &self.js_name;
285    let js_name_str = format!("{js_name_raw}\0");
286    let iterator_implementation = self.gen_iterator_property(class, name);
287    let async_iterator_implementation = self.gen_async_iterator_property(class, name);
288    let (object_finalize_impl, to_napi_value_impl, javascript_class_ext_impl) = if self.has_lifetime
289    {
290      let name = quote! { #name<'_javascript_function_scope> };
291      (
292        quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::ObjectFinalize for #name {} },
293        quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::ToNapiValue for #name },
294        quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::JavaScriptClassExt for #name },
295      )
296    } else {
297      (
298        quote! { impl napi::bindgen_prelude::ObjectFinalize for #name {} },
299        quote! { impl napi::bindgen_prelude::ToNapiValue for #name },
300        quote! { impl napi::bindgen_prelude::JavaScriptClassExt for #name },
301      )
302    };
303    let finalize_trait = if class.use_custom_finalize {
304      quote! {}
305    } else {
306      quote! {
307        #[automatically_derived]
308        #object_finalize_impl
309      }
310    };
311    quote! {
312      #[automatically_derived]
313      #to_napi_value_impl {
314        unsafe fn to_napi_value(
315          env: napi::sys::napi_env,
316          val: #name
317        ) -> napi::Result<napi::bindgen_prelude::sys::napi_value> {
318          if let Some(ctor_ref) = napi::__private::get_class_constructor(#js_name_str) {
319            let mut wrapped_value = Box::into_raw(Box::new(val));
320            if wrapped_value as usize == 0x1 {
321              wrapped_value = Box::into_raw(Box::new(0u8)).cast();
322            }
323            let instance_value = napi::bindgen_prelude::new_instance::<#name>(env, wrapped_value.cast(), ctor_ref)?;
324            #iterator_implementation
325            #async_iterator_implementation
326            Ok(instance_value)
327          } else {
328            Err(napi::bindgen_prelude::Error::new(
329              napi::bindgen_prelude::Status::InvalidArg, format!("Failed to get constructor of class `{}` in `ToNapiValue`", #js_name_raw))
330            )
331          }
332        }
333      }
334
335      #finalize_trait
336
337      #[automatically_derived]
338      #javascript_class_ext_impl {
339        fn into_instance<'scope>(self, env: &'scope napi::Env) -> napi::Result<napi::bindgen_prelude::ClassInstance<'scope, Self>>
340         {
341          if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) {
342            unsafe {
343              let wrapped_value = Box::into_raw(Box::new(self));
344              let instance_value = napi::bindgen_prelude::new_instance::<#name>(env.raw(), wrapped_value as *mut _ as *mut std::ffi::c_void, ctor_ref)?;
345              Ok(napi::bindgen_prelude::ClassInstance::new(instance_value, env.raw(), wrapped_value))
346            }
347          } else {
348            Err(napi::bindgen_prelude::Error::new(
349              napi::bindgen_prelude::Status::InvalidArg, format!("Failed to get constructor of class `{}`", #js_name_raw))
350            )
351          }
352        }
353
354        fn into_reference(self, env: napi::Env) -> napi::Result<napi::bindgen_prelude::Reference<Self>> {
355          if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) {
356            unsafe {
357              let mut wrapped_value = Box::into_raw(Box::new(self));
358              if wrapped_value as usize == 0x1 {
359                wrapped_value = Box::into_raw(Box::new(0u8)).cast();
360              }
361              let instance_value = napi::bindgen_prelude::new_instance::<#name>(env.raw(), wrapped_value.cast(), ctor_ref)?;
362              {
363                let env = env.raw();
364                #iterator_implementation
365                #async_iterator_implementation
366              }
367              napi::bindgen_prelude::Reference::<#name>::from_value_ptr(wrapped_value.cast(), env.raw())
368            }
369          } else {
370            Err(napi::bindgen_prelude::Error::new(
371              napi::bindgen_prelude::Status::InvalidArg, format!("Failed to get constructor of class `{}`", #js_name_raw))
372            )
373          }
374        }
375
376        fn instance_of<'env, V: napi::JsValue<'env>>(env: &napi::bindgen_prelude::Env, value: &V) -> napi::bindgen_prelude::Result<bool> {
377          if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) {
378            let mut ctor = std::ptr::null_mut();
379            napi::check_status!(
380              unsafe { napi::sys::napi_get_reference_value(env.raw(), ctor_ref, &mut ctor) },
381              "Failed to get constructor reference of class `{}`",
382              #js_name_str
383            )?;
384            let mut is_instance_of = false;
385            napi::check_status!(
386              unsafe { napi::sys::napi_instanceof(env.raw(), value.value().value, ctor, &mut is_instance_of) },
387              "Failed to run instanceof for class `{}`",
388              #js_name_str
389            )?;
390            Ok(is_instance_of)
391          } else {
392            Err(napi::Error::new(napi::Status::GenericFailure, format!("Failed to get constructor of class `{}`", #js_name_str)))
393          }
394        }
395      }
396    }
397  }
398
399  fn gen_iterator_property(&self, class: &NapiClass, name: &Ident) -> TokenStream {
400    if !class.implement_iterator {
401      return quote! {};
402    }
403    quote! {
404      unsafe { napi::__private::create_iterator::<#name>(env, instance_value, wrapped_value); }
405    }
406  }
407
408  fn gen_async_iterator_property(&self, class: &NapiClass, name: &Ident) -> TokenStream {
409    if !class.implement_async_iterator {
410      return quote! {};
411    }
412    // Note: `create_async_iterator` is NOT unsafe, unlike `create_iterator`.
413    // `create_iterator` is unsafe because `ScopedGenerator<'a>` has a lifetime parameter,
414    // requiring the caller to uphold lifetime invariants. `create_async_iterator` uses
415    // `AsyncGenerator` whose Future must be `Send + 'static`, so all data is owned and
416    // no lifetime invariants need to be upheld by the caller.
417    quote! {
418      napi::__private::create_async_iterator::<#name>(env, instance_value, wrapped_value);
419    }
420  }
421
422  fn gen_to_napi_value_ctor_impl(&self, class: &NapiClass) -> TokenStream {
423    let name = &self.name;
424    let js_name_without_null = &self.js_name;
425    let js_name_str = format!("{}\0", &self.js_name);
426
427    let mut field_conversions = vec![];
428    let mut field_destructions = vec![];
429
430    for field in class.fields.iter() {
431      let ty = &field.ty;
432
433      match &field.name {
434        syn::Member::Named(ident) => {
435          // alias here prevents field name shadowing
436          let alias_ident = format_ident!("{}_", ident);
437          field_destructions.push(quote! { #ident: #alias_ident });
438          field_conversions.push(
439            quote! { <#ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #alias_ident)? },
440          );
441        }
442        syn::Member::Unnamed(i) => {
443          let arg_name = format_ident!("arg{}", i);
444          field_destructions.push(quote! { #arg_name });
445          field_conversions.push(
446            quote! { <#ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #arg_name)? },
447          );
448        }
449      }
450    }
451
452    let destructed_fields = if class.is_tuple {
453      quote! {
454        Self (#(#field_destructions),*)
455      }
456    } else {
457      quote! {
458        Self {#(#field_destructions),*}
459      }
460    };
461
462    let finalize_trait = if class.use_custom_finalize {
463      quote! {}
464    } else if self.has_lifetime {
465      quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::ObjectFinalize for #name<'_javascript_function_scope> {} }
466    } else {
467      quote! { impl napi::bindgen_prelude::ObjectFinalize for #name {} }
468    };
469
470    let to_napi_value_impl = if self.has_lifetime {
471      quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::ToNapiValue for #name<'_javascript_function_scope> }
472    } else {
473      quote! { impl napi::bindgen_prelude::ToNapiValue for #name }
474    };
475
476    quote! {
477      #[automatically_derived]
478      #to_napi_value_impl {
479        unsafe fn to_napi_value(
480          env: napi::bindgen_prelude::sys::napi_env,
481          val: #name,
482        ) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
483          if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) {
484            let mut ctor = std::ptr::null_mut();
485
486            napi::bindgen_prelude::check_status!(
487              napi::bindgen_prelude::sys::napi_get_reference_value(env, ctor_ref, &mut ctor),
488              "Failed to get constructor reference of class `{}`",
489              #js_name_without_null
490            )?;
491
492            let mut instance_value = std::ptr::null_mut();
493            let #destructed_fields = val;
494            let args = vec![#(#field_conversions),*];
495
496            napi::bindgen_prelude::check_status!(
497              napi::bindgen_prelude::sys::napi_new_instance(env, ctor, args.len(), args.as_ptr(), &mut instance_value),
498              "Failed to construct class `{}`",
499              #js_name_without_null
500            )?;
501
502            Ok(instance_value)
503          } else {
504            Err(napi::bindgen_prelude::Error::new(
505              napi::bindgen_prelude::Status::InvalidArg, format!("Failed to get constructor of class `{}`", #js_name_str))
506            )
507          }
508        }
509      }
510      #finalize_trait
511    }
512  }
513
514  fn gen_to_napi_value_obj_impl(&self, obj: &NapiObject) -> TokenStream {
515    let name = &self.name;
516    let name_str = self.name.to_string();
517
518    let mut obj_field_getters = vec![];
519    let mut field_destructions = vec![];
520
521    // For optimized object creation: separate always-set fields from conditionally-set fields
522    let mut value_conversions = vec![];
523    let mut property_descriptors = vec![];
524    let mut conditional_setters = vec![];
525    let mut value_names = vec![];
526
527    for (idx, field) in obj.fields.iter().enumerate() {
528      let field_js_name = &field.js_name;
529      let field_js_name_lit = Literal::string(&format!("{}\0", field.js_name));
530      let mut ty = field.ty.clone();
531      remove_lifetime_in_type(&mut ty);
532      let is_optional_field = if let syn::Type::Path(syn::TypePath {
533        path: syn::Path { segments, .. },
534        ..
535      }) = &ty
536      {
537        if let Some(last_path) = segments.last() {
538          last_path.ident == "Option"
539        } else {
540          false
541        }
542      } else {
543        false
544      };
545
546      // Determine if this field is always set or conditionally set
547      let is_always_set = !is_optional_field || self.use_nullable;
548
549      match &field.name {
550        syn::Member::Named(ident) => {
551          let alias_ident = format_ident!("{}_", ident);
552          field_destructions.push(quote! { #ident: #alias_ident });
553
554          if is_always_set {
555            // This field is always set - use batched approach
556            let value_var = Ident::new(&format!("__obj_value_{}", idx), Span::call_site());
557            value_names.push(value_var.clone());
558
559            if is_optional_field {
560              // Optional with use_nullable=true: set to value or null
561              value_conversions.push(quote! {
562                let #value_var = if let Some(inner) = #alias_ident {
563                  napi::bindgen_prelude::ToNapiValue::to_napi_value(env, inner)?
564                } else {
565                  napi::bindgen_prelude::ToNapiValue::to_napi_value(env, napi::bindgen_prelude::Null)?
566                };
567              });
568            } else {
569              // Non-optional: always set
570              value_conversions.push(quote! {
571                let #value_var = napi::bindgen_prelude::ToNapiValue::to_napi_value(env, #alias_ident)?;
572              });
573            }
574
575            property_descriptors.push(quote! {
576              napi::bindgen_prelude::sys::napi_property_descriptor {
577                utf8name: std::ffi::CStr::from_bytes_with_nul_unchecked(#field_js_name_lit.as_bytes()).as_ptr(),
578                name: std::ptr::null_mut(),
579                method: None,
580                getter: None,
581                setter: None,
582                value: #value_var,
583                attributes: napi::bindgen_prelude::sys::PropertyAttributes::writable
584                  | napi::bindgen_prelude::sys::PropertyAttributes::enumerable
585                  | napi::bindgen_prelude::sys::PropertyAttributes::configurable,
586                data: std::ptr::null_mut(),
587              }
588            });
589          } else {
590            // Optional with use_nullable=false: conditionally set
591            conditional_setters.push(quote! {
592              if #alias_ident.is_some() {
593                obj.set(#field_js_name, #alias_ident)?;
594              }
595            });
596          }
597
598          // Getters remain the same
599          if is_optional_field && !self.use_nullable {
600            obj_field_getters.push(quote! {
601              let #alias_ident: #ty = obj.get(#field_js_name)
602                .map_err(|err| napi::bindgen_prelude::decorate_field_error(err, #name_str, #field_js_name))?;
603            });
604          } else {
605            obj_field_getters.push(quote! {
606              let #alias_ident: #ty = obj.get(#field_js_name)
607                .map_err(|err| napi::bindgen_prelude::decorate_field_error(err, #name_str, #field_js_name))?
608                .ok_or_else(|| napi::bindgen_prelude::missing_field_error(#field_js_name))?;
609            });
610          }
611        }
612        syn::Member::Unnamed(i) => {
613          let arg_name = format_ident!("arg{}", i);
614          field_destructions.push(quote! { #arg_name });
615
616          if is_always_set {
617            // This field is always set - use batched approach
618            let value_var = Ident::new(&format!("__obj_value_{}", idx), Span::call_site());
619            value_names.push(value_var.clone());
620
621            if is_optional_field {
622              // Optional with use_nullable=true: set to value or null
623              value_conversions.push(quote! {
624                let #value_var = if let Some(inner) = #arg_name {
625                  napi::bindgen_prelude::ToNapiValue::to_napi_value(env, inner)?
626                } else {
627                  napi::bindgen_prelude::ToNapiValue::to_napi_value(env, napi::bindgen_prelude::Null)?
628                };
629              });
630            } else {
631              // Non-optional: always set
632              value_conversions.push(quote! {
633                let #value_var = napi::bindgen_prelude::ToNapiValue::to_napi_value(env, #arg_name)?;
634              });
635            }
636
637            property_descriptors.push(quote! {
638              napi::bindgen_prelude::sys::napi_property_descriptor {
639                utf8name: std::ffi::CStr::from_bytes_with_nul_unchecked(#field_js_name_lit.as_bytes()).as_ptr(),
640                name: std::ptr::null_mut(),
641                method: None,
642                getter: None,
643                setter: None,
644                value: #value_var,
645                attributes: napi::bindgen_prelude::sys::PropertyAttributes::writable
646                  | napi::bindgen_prelude::sys::PropertyAttributes::enumerable
647                  | napi::bindgen_prelude::sys::PropertyAttributes::configurable,
648                data: std::ptr::null_mut(),
649              }
650            });
651          } else {
652            // Optional with use_nullable=false: conditionally set
653            conditional_setters.push(quote! {
654              if #arg_name.is_some() {
655                obj.set(#field_js_name, #arg_name)?;
656              }
657            });
658          }
659
660          // Getters remain the same
661          if is_optional_field && !self.use_nullable {
662            obj_field_getters.push(quote! { let #arg_name: #ty = obj.get(#field_js_name)?; });
663          } else {
664            obj_field_getters.push(quote! {
665              let #arg_name: #ty = obj.get(#field_js_name)?
666                .ok_or_else(|| napi::bindgen_prelude::missing_field_error(#field_js_name))?;
667            });
668          }
669        }
670      }
671    }
672
673    let destructed_fields = if obj.is_tuple {
674      quote! {
675        Self (#(#field_destructions),*)
676      }
677    } else {
678      quote! {
679        Self {#(#field_destructions),*}
680      }
681    };
682
683    let name_with_lifetime = if self.has_lifetime {
684      quote! { #name<'_javascript_function_scope> }
685    } else {
686      quote! { #name }
687    };
688    let (from_napi_value_impl, to_napi_value_impl, validate_napi_value_impl, type_name_impl) =
689      if self.has_lifetime {
690        (
691          quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::FromNapiValue for #name<'_javascript_function_scope> },
692          quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::ToNapiValue for #name<'_javascript_function_scope> },
693          quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::ValidateNapiValue for #name<'_javascript_function_scope> },
694          quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::TypeName for #name<'_javascript_function_scope> },
695        )
696      } else {
697        (
698          quote! { impl napi::bindgen_prelude::FromNapiValue for #name },
699          quote! { impl napi::bindgen_prelude::ToNapiValue for #name },
700          quote! { impl napi::bindgen_prelude::ValidateNapiValue for #name },
701          quote! { impl napi::bindgen_prelude::TypeName for #name },
702        )
703      };
704
705    // Generate object creation code
706    let object_creation = if conditional_setters.is_empty() {
707      // All fields are always set - use fully batched approach
708      quote! {
709        // Convert all values first, so error handling works correctly
710        #(#value_conversions)*
711
712        let properties = [
713          #(#property_descriptors),*
714        ];
715
716        let obj_ptr = napi::bindgen_prelude::create_object_with_properties(env, &properties)?;
717        Ok(obj_ptr)
718      }
719    } else {
720      // Some fields are conditionally set - use batched for always-set, then add conditionals
721      quote! {
722        // Convert all always-set values first
723        #(#value_conversions)*
724
725        let properties = [
726          #(#property_descriptors),*
727        ];
728
729        let obj_ptr = napi::bindgen_prelude::create_object_with_properties(env, &properties)?;
730
731        // Wrap in Object for conditional field setters
732        let mut obj = napi::bindgen_prelude::Object::from_raw(env, obj_ptr);
733
734        #(#conditional_setters)*
735
736        Ok(obj_ptr)
737      }
738    };
739
740    let to_napi_value = if obj.object_to_js {
741      quote! {
742        #[automatically_derived]
743        #to_napi_value_impl {
744          unsafe fn to_napi_value(env: napi::bindgen_prelude::sys::napi_env, val: #name_with_lifetime) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
745            let #destructed_fields = val;
746            #object_creation
747          }
748        }
749      }
750    } else {
751      quote! {}
752    };
753
754    let from_napi_value = if obj.object_from_js {
755      let return_type = if self.has_lifetime {
756        quote! { #name<'_javascript_function_scope> }
757      } else {
758        quote! { #name }
759      };
760      quote! {
761        #[automatically_derived]
762        #from_napi_value_impl {
763          unsafe fn from_napi_value(
764            env: napi::bindgen_prelude::sys::napi_env,
765            napi_val: napi::bindgen_prelude::sys::napi_value
766          ) -> napi::bindgen_prelude::Result<#return_type> {
767            #[allow(unused_variables)]
768            let env_wrapper = napi::bindgen_prelude::Env::from(env);
769            #[allow(unused_mut)]
770            let mut obj = napi::bindgen_prelude::Object::from_napi_value(env, napi_val)?;
771
772            #(#obj_field_getters)*
773
774            let val = #destructed_fields;
775
776            Ok(val)
777          }
778        }
779
780        #[automatically_derived]
781        #validate_napi_value_impl {}
782      }
783    } else {
784      quote! {}
785    };
786
787    quote! {
788      #[automatically_derived]
789      #type_name_impl {
790        fn type_name() -> &'static str {
791          #name_str
792        }
793
794        fn value_type() -> napi::ValueType {
795          napi::ValueType::Object
796        }
797      }
798
799      #to_napi_value
800
801      #from_napi_value
802    }
803  }
804
805  fn gen_default_getters_setters(&self, class: &NapiClass) -> Vec<(String, TokenStream)> {
806    let mut getters_setters = vec![];
807    let struct_name = &self.name;
808    let js_name_str = &self.js_name;
809
810    for field in class.fields.iter() {
811      let field_ident = &field.name;
812      let field_name = match &field.name {
813        syn::Member::Named(ident) => ident.to_string(),
814        syn::Member::Unnamed(i) => format!("field{}", i.index),
815      };
816      let ty = &field.ty;
817
818      let getter_name = Ident::new(
819        &format!("get_{}", rm_raw_prefix(&field_name)),
820        Span::call_site(),
821      );
822      let setter_name = Ident::new(
823        &format!("set_{}", rm_raw_prefix(&field_name)),
824        Span::call_site(),
825      );
826
827      if field.getter {
828        let default_to_napi_value_convert = quote! {
829          let val = &mut obj.#field_ident;
830          unsafe { <&mut #ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, val) }
831        };
832        let to_napi_value_convert = if let syn::Type::Path(syn::TypePath {
833          path: syn::Path { segments, .. },
834          ..
835        }) = ty
836        {
837          if let Some(syn::PathSegment { ident, .. }) = segments.last() {
838            if STRUCT_FIELD_SPECIAL_CASE.iter().any(|name| ident == name) {
839              quote! {
840                let val = obj.#field_ident.as_mut();
841                unsafe { napi::bindgen_prelude::ToNapiValue::to_napi_value(env, val) }
842              }
843            } else {
844              default_to_napi_value_convert
845            }
846          } else {
847            default_to_napi_value_convert
848          }
849        } else {
850          default_to_napi_value_convert
851        };
852        let tracing_debug = gen_tracing_debug(js_name_str, &field.js_name);
853        getters_setters.push((
854          field.js_name.clone(),
855          quote! {
856            extern "C" fn #getter_name(
857              env: napi::bindgen_prelude::sys::napi_env,
858              cb: napi::bindgen_prelude::sys::napi_callback_info
859            ) -> napi::bindgen_prelude::sys::napi_value {
860              #tracing_debug
861              napi::bindgen_prelude::CallbackInfo::<0>::new(env, cb, Some(0), false)
862                .and_then(|mut cb| cb.unwrap_borrow_mut::<#struct_name>())
863                .and_then(|obj| {
864                  #to_napi_value_convert
865                })
866                .unwrap_or_else(|e| {
867                  unsafe { napi::bindgen_prelude::JsError::from(e).throw_into(env) };
868                  std::ptr::null_mut::<napi::bindgen_prelude::sys::napi_value__>()
869                })
870            }
871          },
872        ));
873      }
874
875      if field.setter {
876        let setter_tracing_debug =
877          gen_tracing_debug(js_name_str, &format!("set_{}", field.js_name));
878        getters_setters.push((
879          field.js_name.clone(),
880          quote! {
881            extern "C" fn #setter_name(
882              env: napi::bindgen_prelude::sys::napi_env,
883              cb: napi::bindgen_prelude::sys::napi_callback_info
884            ) -> napi::bindgen_prelude::sys::napi_value {
885              #setter_tracing_debug
886              napi::bindgen_prelude::CallbackInfo::<1>::new(env, cb, Some(1), false)
887                .and_then(|mut cb_info| unsafe {
888                  cb_info.unwrap_borrow_mut::<#struct_name>()
889                    .and_then(|obj| {
890                      <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb_info.get_arg(0))
891                        .and_then(move |val| {
892                          obj.#field_ident = val;
893                          <() as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, ())
894                        })
895                    })
896                })
897                .unwrap_or_else(|e| {
898                  unsafe { napi::bindgen_prelude::JsError::from(e).throw_into(env) };
899                  std::ptr::null_mut::<napi::bindgen_prelude::sys::napi_value__>()
900                })
901            }
902          },
903        ));
904      }
905    }
906
907    getters_setters
908  }
909
910  fn gen_register(&self, class: &NapiClass) -> TokenStream {
911    let name = &self.name;
912    let struct_register_name = &self.register_name;
913    let js_name = format!("{}\0", self.js_name);
914    let implement_iterator = class.implement_iterator;
915    let mut props = vec![];
916
917    if class.ctor {
918      props.push(quote! { napi::bindgen_prelude::Property::new().with_utf8_name("constructor").unwrap().with_ctor(constructor) });
919    }
920
921    for field in class.fields.iter() {
922      let field_name = match &field.name {
923        syn::Member::Named(ident) => ident.to_string(),
924        syn::Member::Unnamed(i) => format!("field{}", i.index),
925      };
926
927      if !field.getter {
928        continue;
929      }
930
931      let js_name = &field.js_name;
932      let mut attribute = super::PROPERTY_ATTRIBUTE_DEFAULT;
933      if field.writable {
934        attribute |= super::PROPERTY_ATTRIBUTE_WRITABLE;
935      }
936      if field.enumerable {
937        attribute |= super::PROPERTY_ATTRIBUTE_ENUMERABLE;
938      }
939      if field.configurable {
940        attribute |= super::PROPERTY_ATTRIBUTE_CONFIGURABLE;
941      }
942
943      let mut prop = quote! {
944        napi::bindgen_prelude::Property::new().with_utf8_name(#js_name)
945          .unwrap()
946          .with_property_attributes(napi::bindgen_prelude::PropertyAttributes::from_bits(#attribute).unwrap())
947      };
948
949      if field.getter {
950        let getter_name = Ident::new(
951          &format!("get_{}", rm_raw_prefix(&field_name)),
952          Span::call_site(),
953        );
954        (quote! { .with_getter(#getter_name) }).to_tokens(&mut prop);
955      }
956
957      if field.writable && field.setter {
958        let setter_name = Ident::new(
959          &format!("set_{}", rm_raw_prefix(&field_name)),
960          Span::call_site(),
961        );
962        (quote! { .with_setter(#setter_name) }).to_tokens(&mut prop);
963      }
964
965      props.push(prop);
966    }
967    let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref());
968    quote! {
969      #[cfg(all(not(test), not(target_family = "wasm")))]
970      napi::ctor::declarative::ctor! {
971        #[allow(non_snake_case)]
972        #[allow(clippy::all)]
973        #[ctor(unsafe)]
974        fn #struct_register_name() {
975          napi::__private::register_class(std::any::TypeId::of::<#name>(), #js_mod_ident, #js_name, vec![#(#props),*], #implement_iterator);
976        }
977      }
978
979      #[allow(non_snake_case)]
980      #[allow(clippy::all)]
981      #[cfg(all(not(test), target_family = "wasm"))]
982      #[no_mangle]
983      extern "C" fn #struct_register_name() {
984        napi::__private::register_class(std::any::TypeId::of::<#name>(), #js_mod_ident, #js_name, vec![#(#props),*], #implement_iterator);
985      }
986    }
987  }
988
989  fn gen_to_napi_value_structured_enum_impl(
990    &self,
991    structured_enum: &NapiStructuredEnum,
992  ) -> TokenStream {
993    let name = &self.name;
994    let name_str = self.name.to_string();
995    let discriminant = structured_enum.discriminant.as_str();
996    let discriminant_null_terminated = format!("{}\0", discriminant);
997
998    let mut variant_arm_setters = vec![];
999    let mut variant_arm_getters = vec![];
1000
1001    for variant in structured_enum.variants.iter() {
1002      let variant_name = &variant.name;
1003      let mut variant_name_str = variant_name.to_string();
1004      if let Some(case) = structured_enum.discriminant_case {
1005        variant_name_str = to_case(variant_name_str, case);
1006      }
1007
1008      let mut obj_field_getters = vec![];
1009      let mut field_destructions = vec![];
1010
1011      // For optimized object creation
1012      let mut value_conversions = vec![];
1013      let mut property_descriptors = vec![];
1014      let mut conditional_setters = vec![];
1015
1016      // First property is always the discriminant
1017      let discriminant_value_var = Ident::new("__discriminant_value", Span::call_site());
1018      value_conversions.push(quote! {
1019        let #discriminant_value_var = napi::bindgen_prelude::ToNapiValue::to_napi_value(env, #variant_name_str)?;
1020      });
1021      property_descriptors.push(quote! {
1022        napi::bindgen_prelude::sys::napi_property_descriptor {
1023          utf8name: std::ffi::CStr::from_bytes_with_nul_unchecked(#discriminant_null_terminated.as_bytes()).as_ptr(),
1024          name: std::ptr::null_mut(),
1025          method: None,
1026          getter: None,
1027          setter: None,
1028          value: #discriminant_value_var,
1029          attributes: napi::bindgen_prelude::sys::PropertyAttributes::writable
1030                  | napi::bindgen_prelude::sys::PropertyAttributes::enumerable
1031                  | napi::bindgen_prelude::sys::PropertyAttributes::configurable,
1032          data: std::ptr::null_mut(),
1033        }
1034      });
1035
1036      for (idx, field) in variant.fields.iter().enumerate() {
1037        let field_js_name = &field.js_name;
1038        let field_js_name_lit = Literal::string(&format!("{}\0", field.js_name));
1039        let mut ty = field.ty.clone();
1040        remove_lifetime_in_type(&mut ty);
1041        let is_optional_field = if let syn::Type::Path(syn::TypePath {
1042          path: syn::Path { segments, .. },
1043          ..
1044        }) = &ty
1045        {
1046          if let Some(last_path) = segments.last() {
1047            last_path.ident == "Option"
1048          } else {
1049            false
1050          }
1051        } else {
1052          false
1053        };
1054
1055        // Determine if this field is always set or conditionally set
1056        let is_always_set = !is_optional_field || self.use_nullable;
1057
1058        match &field.name {
1059          syn::Member::Named(ident) => {
1060            let alias_ident = format_ident!("{}_", ident);
1061            field_destructions.push(quote! { #ident: #alias_ident });
1062
1063            if is_always_set {
1064              // This field is always set - use batched approach
1065              let value_var = Ident::new(&format!("__variant_value_{}", idx), Span::call_site());
1066
1067              if is_optional_field {
1068                // Optional with use_nullable=true: set to value or null
1069                value_conversions.push(quote! {
1070                  let #value_var = if let Some(inner) = #alias_ident {
1071                    napi::bindgen_prelude::ToNapiValue::to_napi_value(env, inner)?
1072                  } else {
1073                    napi::bindgen_prelude::ToNapiValue::to_napi_value(env, napi::bindgen_prelude::Null)?
1074                  };
1075                });
1076              } else {
1077                // Non-optional: always set
1078                value_conversions.push(quote! {
1079                  let #value_var = napi::bindgen_prelude::ToNapiValue::to_napi_value(env, #alias_ident)?;
1080                });
1081              }
1082
1083              property_descriptors.push(quote! {
1084                napi::bindgen_prelude::sys::napi_property_descriptor {
1085                  utf8name: std::ffi::CStr::from_bytes_with_nul_unchecked(#field_js_name_lit.as_bytes()).as_ptr(),
1086                  name: std::ptr::null_mut(),
1087                  method: None,
1088                  getter: None,
1089                  setter: None,
1090                  value: #value_var,
1091                  attributes: napi::bindgen_prelude::sys::PropertyAttributes::writable
1092                  | napi::bindgen_prelude::sys::PropertyAttributes::enumerable
1093                  | napi::bindgen_prelude::sys::PropertyAttributes::configurable,
1094                  data: std::ptr::null_mut(),
1095                }
1096              });
1097            } else {
1098              // Optional with use_nullable=false: conditionally set
1099              conditional_setters.push(quote! {
1100                if #alias_ident.is_some() {
1101                  obj.set(#field_js_name, #alias_ident)?;
1102                }
1103              });
1104            }
1105
1106            // Getters remain the same
1107            if is_optional_field && !self.use_nullable {
1108              obj_field_getters.push(quote! {
1109                let #alias_ident: #ty = obj.get(#field_js_name)
1110                  .map_err(|err| napi::bindgen_prelude::decorate_field_error(err, #name_str, #field_js_name))?;
1111              });
1112            } else {
1113              obj_field_getters.push(quote! {
1114                let #alias_ident: #ty = obj.get(#field_js_name)
1115                  .map_err(|err| napi::bindgen_prelude::decorate_field_error(err, #name_str, #field_js_name))?
1116                  .ok_or_else(|| napi::bindgen_prelude::missing_field_error(#field_js_name))?;
1117              });
1118            }
1119          }
1120          syn::Member::Unnamed(i) => {
1121            let arg_name = format_ident!("arg{}", i);
1122            field_destructions.push(quote! { #arg_name });
1123
1124            if is_always_set {
1125              // This field is always set - use batched approach
1126              let value_var = Ident::new(&format!("__variant_value_{}", idx), Span::call_site());
1127
1128              if is_optional_field {
1129                // Optional with use_nullable=true: set to value or null
1130                value_conversions.push(quote! {
1131                  let #value_var = if let Some(inner) = #arg_name {
1132                    napi::bindgen_prelude::ToNapiValue::to_napi_value(env, inner)?
1133                  } else {
1134                    napi::bindgen_prelude::ToNapiValue::to_napi_value(env, napi::bindgen_prelude::Null)?
1135                  };
1136                });
1137              } else {
1138                // Non-optional: always set
1139                value_conversions.push(quote! {
1140                  let #value_var = napi::bindgen_prelude::ToNapiValue::to_napi_value(env, #arg_name)?;
1141                });
1142              }
1143
1144              property_descriptors.push(quote! {
1145                napi::bindgen_prelude::sys::napi_property_descriptor {
1146                  utf8name: std::ffi::CStr::from_bytes_with_nul_unchecked(#field_js_name_lit.as_bytes()).as_ptr(),
1147                  name: std::ptr::null_mut(),
1148                  method: None,
1149                  getter: None,
1150                  setter: None,
1151                  value: #value_var,
1152                  attributes: napi::bindgen_prelude::sys::PropertyAttributes::writable
1153                  | napi::bindgen_prelude::sys::PropertyAttributes::enumerable
1154                  | napi::bindgen_prelude::sys::PropertyAttributes::configurable,
1155                  data: std::ptr::null_mut(),
1156                }
1157              });
1158            } else {
1159              // Optional with use_nullable=false: conditionally set
1160              conditional_setters.push(quote! {
1161                if #arg_name.is_some() {
1162                  obj.set(#field_js_name, #arg_name)?;
1163                }
1164              });
1165            }
1166
1167            // Getters remain the same
1168            if is_optional_field && !self.use_nullable {
1169              obj_field_getters.push(quote! { let #arg_name: #ty = obj.get(#field_js_name)?; });
1170            } else {
1171              obj_field_getters.push(quote! {
1172                let #arg_name: #ty = obj.get(#field_js_name)?
1173                  .ok_or_else(|| napi::bindgen_prelude::missing_field_error(#field_js_name))?;
1174              });
1175            }
1176          }
1177        }
1178      }
1179
1180      let destructed_fields = if variant.is_tuple {
1181        quote! {
1182          Self::#variant_name (#(#field_destructions),*)
1183        }
1184      } else {
1185        quote! {
1186          Self::#variant_name {#(#field_destructions),*}
1187        }
1188      };
1189
1190      // Generate object creation for this variant
1191      let variant_object_creation = if conditional_setters.is_empty() {
1192        // All fields are always set - use fully batched approach
1193        quote! {
1194          #(#value_conversions)*
1195
1196          let properties = [
1197            #(#property_descriptors),*
1198          ];
1199
1200          napi::bindgen_prelude::create_object_with_properties(env, &properties)
1201        }
1202      } else {
1203        // Some fields are conditionally set
1204        quote! {
1205          #(#value_conversions)*
1206
1207          let properties = [
1208            #(#property_descriptors),*
1209          ];
1210
1211          let obj_ptr = napi::bindgen_prelude::create_object_with_properties(env, &properties)?;
1212          let mut obj = napi::bindgen_prelude::Object::from_raw(env, obj_ptr);
1213
1214          #(#conditional_setters)*
1215
1216          Ok(obj_ptr)
1217        }
1218      };
1219
1220      variant_arm_setters.push(quote! {
1221        #destructed_fields => {
1222          #variant_object_creation
1223        },
1224      });
1225
1226      variant_arm_getters.push(quote! {
1227        #variant_name_str => {
1228          #(#obj_field_getters)*
1229          #destructed_fields
1230        },
1231      })
1232    }
1233
1234    let to_napi_value = if structured_enum.object_to_js {
1235      quote! {
1236        impl napi::bindgen_prelude::ToNapiValue for #name {
1237          unsafe fn to_napi_value(env: napi::bindgen_prelude::sys::napi_env, val: #name) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
1238            match val {
1239              #(#variant_arm_setters)*
1240            }
1241          }
1242        }
1243      }
1244    } else {
1245      quote! {}
1246    };
1247
1248    let from_napi_value = if structured_enum.object_from_js {
1249      quote! {
1250        impl napi::bindgen_prelude::FromNapiValue for #name {
1251          unsafe fn from_napi_value(
1252            env: napi::bindgen_prelude::sys::napi_env,
1253            napi_val: napi::bindgen_prelude::sys::napi_value
1254          ) -> napi::bindgen_prelude::Result<Self> {
1255            #[allow(unused_variables)]
1256            let env_wrapper = napi::bindgen_prelude::Env::from(env);
1257            #[allow(unused_mut)]
1258            let mut obj = napi::bindgen_prelude::Object::from_napi_value(env, napi_val)?;
1259            let type_: String = obj.get(#discriminant)
1260              .map_err(|err| napi::bindgen_prelude::decorate_field_error(err, #name_str, #discriminant))?
1261              .ok_or_else(|| napi::bindgen_prelude::missing_field_error(#discriminant))?;
1262            let val = match type_.as_str() {
1263              #(#variant_arm_getters)*
1264              _ => return Err(napi::bindgen_prelude::Error::new(
1265                napi::bindgen_prelude::Status::InvalidArg,
1266                format!("Unknown variant `{}`", type_),
1267              )),
1268            };
1269
1270            Ok(val)
1271          }
1272        }
1273
1274        impl napi::bindgen_prelude::ValidateNapiValue for #name {}
1275      }
1276    } else {
1277      quote! {}
1278    };
1279
1280    quote! {
1281      impl napi::bindgen_prelude::TypeName for #name {
1282        fn type_name() -> &'static str {
1283          #name_str
1284        }
1285
1286        fn value_type() -> napi::ValueType {
1287          napi::ValueType::Object
1288        }
1289      }
1290
1291      #to_napi_value
1292
1293      #from_napi_value
1294    }
1295  }
1296
1297  fn gen_napi_value_transparent_impl(&self, transparent: &NapiTransparent) -> TokenStream {
1298    let name = &self.name;
1299    let name = if self.has_lifetime {
1300      quote! { #name<'_> }
1301    } else {
1302      quote! { #name }
1303    };
1304    let inner_type = transparent.ty.clone().into_token_stream();
1305
1306    let to_napi_value = if transparent.object_to_js {
1307      quote! {
1308        #[automatically_derived]
1309        impl napi::bindgen_prelude::ToNapiValue for #name {
1310          unsafe fn to_napi_value(
1311            env: napi::bindgen_prelude::sys::napi_env,
1312            val: Self
1313          ) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
1314            <#inner_type>::to_napi_value(env, val.0)
1315          }
1316        }
1317      }
1318    } else {
1319      quote! {}
1320    };
1321
1322    let from_napi_value = if transparent.object_from_js {
1323      quote! {
1324        #[automatically_derived]
1325        impl napi::bindgen_prelude::FromNapiValue for #name {
1326          unsafe fn from_napi_value(
1327            env: napi::bindgen_prelude::sys::napi_env,
1328            napi_val: napi::bindgen_prelude::sys::napi_value
1329          ) -> napi::bindgen_prelude::Result<Self> {
1330            Ok(Self(<#inner_type>::from_napi_value(env, napi_val)?))
1331          }
1332        }
1333      }
1334    } else {
1335      quote! {}
1336    };
1337
1338    quote! {
1339      #[automatically_derived]
1340      impl napi::bindgen_prelude::TypeName for #name {
1341        fn type_name() -> &'static str {
1342          <#inner_type>::type_name()
1343        }
1344
1345        fn value_type() -> napi::ValueType {
1346          <#inner_type>::value_type()
1347        }
1348      }
1349
1350      #[automatically_derived]
1351      impl napi::bindgen_prelude::ValidateNapiValue for #name {
1352        unsafe fn validate(
1353          env: napi::bindgen_prelude::sys::napi_env,
1354          napi_val: napi::bindgen_prelude::sys::napi_value
1355        ) -> napi::bindgen_prelude::Result<napi::sys::napi_value> {
1356          <#inner_type>::validate(env, napi_val)
1357        }
1358      }
1359
1360      #to_napi_value
1361
1362      #from_napi_value
1363    }
1364  }
1365
1366  fn gen_napi_value_array_impl(&self, array: &NapiArray) -> TokenStream {
1367    let name = &self.name;
1368    let name_str = self.name.to_string();
1369
1370    let mut obj_field_setters = vec![];
1371    let mut obj_field_getters = vec![];
1372    let mut field_destructions = vec![];
1373
1374    for field in array.fields.iter() {
1375      let mut ty = field.ty.clone();
1376      remove_lifetime_in_type(&mut ty);
1377      let is_optional_field = if let syn::Type::Path(syn::TypePath {
1378        path: syn::Path { segments, .. },
1379        ..
1380      }) = &ty
1381      {
1382        if let Some(last_path) = segments.last() {
1383          last_path.ident == "Option"
1384        } else {
1385          false
1386        }
1387      } else {
1388        false
1389      };
1390
1391      if let syn::Member::Unnamed(i) = &field.name {
1392        let arg_name = format_ident!("arg{}", i);
1393        let field_index = i.index;
1394        field_destructions.push(quote! { #arg_name });
1395        if is_optional_field {
1396          obj_field_setters.push(match self.use_nullable {
1397            false => quote! {
1398              if #arg_name.is_some() {
1399                array.set(#field_index, #arg_name)?;
1400              }
1401            },
1402            true => quote! {
1403              if let Some(#arg_name) = #arg_name {
1404                array.set(#field_index, #arg_name)?;
1405              } else {
1406                array.set(#field_index, napi::bindgen_prelude::Null)?;
1407              }
1408            },
1409          });
1410        } else {
1411          obj_field_setters.push(quote! { array.set(#field_index, #arg_name)?; });
1412        }
1413        if is_optional_field && !self.use_nullable {
1414          obj_field_getters.push(quote! { let #arg_name: #ty = array.get(#field_index)?; });
1415        } else {
1416          obj_field_getters.push(quote! {
1417            let #arg_name: #ty = array.get(#field_index)?.ok_or_else(|| napi::bindgen_prelude::Error::new(
1418              napi::bindgen_prelude::Status::InvalidArg,
1419              format!("Failed to get element with index `{}`", #field_index),
1420            ))?;
1421          });
1422        }
1423      }
1424    }
1425
1426    let destructed_fields = quote! {
1427      Self (#(#field_destructions),*)
1428    };
1429
1430    let name_with_lifetime = if self.has_lifetime {
1431      quote! { #name<'_javascript_function_scope> }
1432    } else {
1433      quote! { #name }
1434    };
1435    let (from_napi_value_impl, to_napi_value_impl, validate_napi_value_impl, type_name_impl) =
1436      if self.has_lifetime {
1437        (
1438          quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::FromNapiValue for #name<'_javascript_function_scope> },
1439          quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::ToNapiValue for #name<'_javascript_function_scope> },
1440          quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::ValidateNapiValue for #name<'_javascript_function_scope> },
1441          quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::TypeName for #name<'_javascript_function_scope> },
1442        )
1443      } else {
1444        (
1445          quote! { impl napi::bindgen_prelude::FromNapiValue for #name },
1446          quote! { impl napi::bindgen_prelude::ToNapiValue for #name },
1447          quote! { impl napi::bindgen_prelude::ValidateNapiValue for #name },
1448          quote! { impl napi::bindgen_prelude::TypeName for #name },
1449        )
1450      };
1451
1452    let array_len = array.fields.len() as u32;
1453
1454    let to_napi_value = if array.object_to_js {
1455      quote! {
1456        #[automatically_derived]
1457        #to_napi_value_impl {
1458          unsafe fn to_napi_value(env: napi::bindgen_prelude::sys::napi_env, val: #name_with_lifetime) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
1459            #[allow(unused_variables)]
1460            let env_wrapper = napi::bindgen_prelude::Env::from(env);
1461            #[allow(unused_mut)]
1462            let mut array = env_wrapper.create_array(#array_len)?;
1463
1464            let #destructed_fields = val;
1465            #(#obj_field_setters)*
1466
1467            napi::bindgen_prelude::Array::to_napi_value(env, array)
1468          }
1469        }
1470      }
1471    } else {
1472      quote! {}
1473    };
1474
1475    let from_napi_value = if array.object_from_js {
1476      let return_type = if self.has_lifetime {
1477        quote! { #name<'_javascript_function_scope> }
1478      } else {
1479        quote! { #name }
1480      };
1481      quote! {
1482        #[automatically_derived]
1483        #from_napi_value_impl {
1484          unsafe fn from_napi_value(
1485            env: napi::bindgen_prelude::sys::napi_env,
1486            napi_val: napi::bindgen_prelude::sys::napi_value
1487          ) -> napi::bindgen_prelude::Result<#return_type> {
1488            #[allow(unused_variables)]
1489            let env_wrapper = napi::bindgen_prelude::Env::from(env);
1490            #[allow(unused_mut)]
1491            let mut array = napi::bindgen_prelude::Array::from_napi_value(env, napi_val)?;
1492
1493            #(#obj_field_getters)*
1494
1495            let val = #destructed_fields;
1496
1497            Ok(val)
1498          }
1499        }
1500
1501        #[automatically_derived]
1502        #validate_napi_value_impl {}
1503      }
1504    } else {
1505      quote! {}
1506    };
1507
1508    quote! {
1509      #[automatically_derived]
1510      #type_name_impl {
1511        fn type_name() -> &'static str {
1512          #name_str
1513        }
1514
1515        fn value_type() -> napi::ValueType {
1516          napi::ValueType::Object
1517        }
1518      }
1519
1520      #to_napi_value
1521
1522      #from_napi_value
1523    }
1524  }
1525}
1526
1527impl TryToTokens for NapiImpl {
1528  fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> {
1529    self.gen_helper_mod()?.to_tokens(tokens);
1530
1531    Ok(())
1532  }
1533}
1534
1535impl NapiImpl {
1536  fn gen_helper_mod(&self) -> BindgenResult<TokenStream> {
1537    if cfg!(test) {
1538      return Ok(quote! {});
1539    }
1540
1541    let name = &self.name;
1542    let name_str = self.name.to_string();
1543    let js_name = format!("{}\0", self.js_name);
1544    let mod_name = Ident::new(
1545      &format!(
1546        "__napi_impl_helper_{}_{}",
1547        name_str,
1548        NAPI_IMPL_ID.fetch_add(1, Ordering::SeqCst)
1549      ),
1550      Span::call_site(),
1551    );
1552
1553    let register_name = &self.register_name;
1554
1555    let mut methods = vec![];
1556    let mut props = HashMap::new();
1557
1558    for item in self.items.iter() {
1559      let js_name = Literal::string(&item.js_name);
1560      let item_str = item.name.to_string();
1561      let intermediate_name = get_intermediate_ident(&item_str);
1562      methods.push(item.try_to_token_stream()?);
1563
1564      let mut attribute = super::PROPERTY_ATTRIBUTE_DEFAULT;
1565      if item.writable {
1566        attribute |= super::PROPERTY_ATTRIBUTE_WRITABLE;
1567      }
1568      if item.enumerable {
1569        attribute |= super::PROPERTY_ATTRIBUTE_ENUMERABLE;
1570      }
1571      if item.configurable {
1572        attribute |= super::PROPERTY_ATTRIBUTE_CONFIGURABLE;
1573      }
1574
1575      let prop = props.entry(&item.js_name).or_insert_with(|| {
1576        quote! {
1577          napi::bindgen_prelude::Property::new().with_utf8_name(#js_name).unwrap().with_property_attributes(napi::bindgen_prelude::PropertyAttributes::from_bits(#attribute).unwrap())
1578        }
1579      });
1580
1581      let appendix = match item.kind {
1582        FnKind::Constructor => quote! { .with_ctor(#intermediate_name) },
1583        FnKind::Getter => quote! { .with_getter(#intermediate_name) },
1584        FnKind::Setter => quote! { .with_setter(#intermediate_name) },
1585        _ => {
1586          if item.fn_self.is_some() {
1587            quote! { .with_method(#intermediate_name) }
1588          } else {
1589            quote! { .with_method(#intermediate_name).with_property_attributes(napi::bindgen_prelude::PropertyAttributes::Static) }
1590          }
1591        }
1592      };
1593
1594      appendix.to_tokens(prop);
1595    }
1596
1597    let mut props: Vec<_> = props.into_iter().collect();
1598    props.sort_by_key(|(_, prop)| prop.to_string());
1599    let props = props.into_iter().map(|(_, prop)| prop);
1600    let props_wasm = props.clone();
1601    let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref());
1602    Ok(quote! {
1603      #[allow(non_snake_case)]
1604      #[allow(clippy::all)]
1605      mod #mod_name {
1606        use super::*;
1607        #(#methods)*
1608
1609        #[cfg(all(not(test), not(target_family = "wasm")))]
1610        napi::ctor::declarative::ctor! {
1611          #[ctor(unsafe)]
1612          fn #register_name() {
1613            napi::__private::register_class(std::any::TypeId::of::<#name>(), #js_mod_ident, #js_name, vec![#(#props),*], false);
1614          }
1615        }
1616
1617        #[cfg(all(not(test), target_family = "wasm"))]
1618        #[no_mangle]
1619        extern "C" fn #register_name() {
1620          napi::__private::register_class(std::any::TypeId::of::<#name>(), #js_mod_ident, #js_name, vec![#(#props_wasm),*], false);
1621        }
1622      }
1623    })
1624  }
1625}
1626
1627pub fn rm_raw_prefix(s: &str) -> &str {
1628  if let Some(stripped) = s.strip_prefix("r#") {
1629    stripped
1630  } else {
1631    s
1632  }
1633}
1634
1635fn remove_lifetime_in_type(ty: &mut syn::Type) {
1636  if let syn::Type::Path(syn::TypePath { path, .. }) = ty {
1637    path.segments.iter_mut().for_each(|segment| {
1638      if let syn::PathArguments::AngleBracketed(ref mut args) = segment.arguments {
1639        args.args.iter_mut().for_each(|arg| match arg {
1640          syn::GenericArgument::Type(ref mut ty) => {
1641            remove_lifetime_in_type(ty);
1642          }
1643          syn::GenericArgument::Lifetime(lifetime) => {
1644            lifetime.ident = Ident::new("_", lifetime.ident.span());
1645          }
1646          _ => {}
1647        });
1648      }
1649    });
1650  }
1651}