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