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