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