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