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