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