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