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