use std::collections::HashMap; use std::sync::atomic::{AtomicU32, Ordering}; use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::ToTokens; use crate::{ codegen::{get_intermediate_ident, js_mod_to_token_stream}, BindgenResult, FnKind, NapiImpl, NapiStruct, NapiStructKind, TryToTokens, }; use crate::{NapiClass, NapiObject, NapiStructuredEnum}; static NAPI_IMPL_ID: AtomicU32 = AtomicU32::new(0); const TYPED_ARRAY_TYPE: &[&str] = &[ "Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array", "Int32Array", "Uint32Array", "Float32Array", "Float64Array", "BigInt64Array", "BigUint64Array", ]; // Generate trait implementations for given Struct. fn gen_napi_value_map_impl( name: &Ident, to_napi_val_impl: TokenStream, has_lifetime: bool, ) -> TokenStream { let name_str = name.to_string(); let name = if has_lifetime { quote! { #name<'_> } } else { quote! { #name } }; let js_name_str = format!("{}\0", name_str); let validate = quote! { unsafe fn validate(env: napi::sys::napi_env, napi_val: napi::sys::napi_value) -> napi::Result { if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) { let mut ctor = std::ptr::null_mut(); napi::check_status!( napi::sys::napi_get_reference_value(env, ctor_ref, &mut ctor), "Failed to get constructor reference of class `{}`", #name_str )?; let mut is_instance_of = false; napi::check_status!( napi::sys::napi_instanceof(env, napi_val, ctor, &mut is_instance_of), "Failed to get external value of class `{}`", #name_str )?; if is_instance_of { Ok(std::ptr::null_mut()) } else { Err(napi::Error::new( napi::Status::InvalidArg, format!("Value is not instanceof class `{}`", #name_str) )) } } else { Err(napi::Error::new( napi::Status::InvalidArg, format!("Failed to get constructor of class `{}`", #name_str) )) } } }; quote! { #[automatically_derived] impl napi::bindgen_prelude::TypeName for #name { fn type_name() -> &'static str { #name_str } fn value_type() -> napi::ValueType { napi::ValueType::Function } } #[automatically_derived] impl napi::bindgen_prelude::TypeName for &#name { fn type_name() -> &'static str { #name_str } fn value_type() -> napi::ValueType { napi::ValueType::Object } } #[automatically_derived] impl napi::bindgen_prelude::TypeName for &mut #name { fn type_name() -> &'static str { #name_str } fn value_type() -> napi::ValueType { napi::ValueType::Object } } #to_napi_val_impl #[automatically_derived] impl napi::bindgen_prelude::FromNapiRef for #name { unsafe fn from_napi_ref( env: napi::bindgen_prelude::sys::napi_env, napi_val: napi::bindgen_prelude::sys::napi_value ) -> napi::bindgen_prelude::Result<&'static Self> { let mut wrapped_val: *mut std::ffi::c_void = std::ptr::null_mut(); napi::bindgen_prelude::check_status!( napi::bindgen_prelude::sys::napi_unwrap(env, napi_val, &mut wrapped_val), "Failed to recover `{}` type from napi value", #name_str, )?; Ok(&*(wrapped_val as *const #name)) } } #[automatically_derived] impl napi::bindgen_prelude::FromNapiMutRef for #name { unsafe fn from_napi_mut_ref( env: napi::bindgen_prelude::sys::napi_env, napi_val: napi::bindgen_prelude::sys::napi_value ) -> napi::bindgen_prelude::Result<&'static mut Self> { let mut wrapped_val: *mut std::ffi::c_void = std::ptr::null_mut(); napi::bindgen_prelude::check_status!( napi::bindgen_prelude::sys::napi_unwrap(env, napi_val, &mut wrapped_val), "Failed to recover `{}` type from napi value", #name_str, )?; Ok(&mut *(wrapped_val as *mut #name)) } } #[automatically_derived] impl napi::bindgen_prelude::FromNapiValue for &#name { unsafe fn from_napi_value( env: napi::bindgen_prelude::sys::napi_env, napi_val: napi::bindgen_prelude::sys::napi_value ) -> napi::bindgen_prelude::Result { napi::bindgen_prelude::FromNapiRef::from_napi_ref(env, napi_val) } } #[automatically_derived] impl napi::bindgen_prelude::FromNapiValue for &mut #name { unsafe fn from_napi_value( env: napi::bindgen_prelude::sys::napi_env, napi_val: napi::bindgen_prelude::sys::napi_value ) -> napi::bindgen_prelude::Result { let mut wrapped_val: *mut std::ffi::c_void = std::ptr::null_mut(); napi::bindgen_prelude::check_status!( napi::bindgen_prelude::sys::napi_unwrap(env, napi_val, &mut wrapped_val), "Failed to recover `{}` type from napi value", #name_str, )?; Ok(&mut *(wrapped_val as *mut #name)) } } #[automatically_derived] impl napi::bindgen_prelude::ValidateNapiValue for &#name { #validate } #[automatically_derived] impl napi::bindgen_prelude::ValidateNapiValue for &mut #name { #validate } } } impl TryToTokens for NapiStruct { fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> { let napi_value_map_impl = self.gen_napi_value_map_impl(); let class_helper_mod = match &self.kind { NapiStructKind::Class(class) => self.gen_helper_mod(class), _ => quote! {}, }; (quote! { #napi_value_map_impl #class_helper_mod }) .to_tokens(tokens); Ok(()) } } impl NapiStruct { fn gen_helper_mod(&self, class: &NapiClass) -> TokenStream { let mod_name = Ident::new(&format!("__napi_helper__{}", self.name), Span::call_site()); let ctor = if class.ctor { self.gen_default_ctor(class) } else { quote! {} }; let mut getters_setters = self.gen_default_getters_setters(class); getters_setters.sort_by(|a, b| a.0.cmp(&b.0)); let register = self.gen_register(class); let getters_setters_token = getters_setters.into_iter().map(|(_, token)| token); quote! { #[allow(clippy::all)] #[allow(non_snake_case)] mod #mod_name { use std::ptr; use super::*; #ctor #(#getters_setters_token)* #register } } } fn gen_default_ctor(&self, class: &NapiClass) -> TokenStream { let name = &self.name; let js_name_str = &self.js_name; let fields_len = class.fields.len(); let mut fields = vec![]; for (i, field) in class.fields.iter().enumerate() { let ty = &field.ty; match &field.name { syn::Member::Named(ident) => fields .push(quote! { #ident: <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb.get_arg(#i))? }), syn::Member::Unnamed(_) => { fields.push(quote! { <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb.get_arg(#i))? }); } } } let construct = if class.is_tuple { quote! { #name (#(#fields),*) } } else { quote! { #name {#(#fields),*} } }; let is_empty_struct_hint = fields_len == 0; let constructor = if class.implement_iterator { quote! { unsafe { cb.construct_generator::<#is_empty_struct_hint, #name>(#js_name_str, #construct) } } } else { quote! { unsafe { cb.construct::<#is_empty_struct_hint, #name>(#js_name_str, #construct) } } }; quote! { extern "C" fn constructor( env: napi::bindgen_prelude::sys::napi_env, cb: napi::bindgen_prelude::sys::napi_callback_info ) -> napi::bindgen_prelude::sys::napi_value { napi::bindgen_prelude::CallbackInfo::<#fields_len>::new(env, cb, None, false) .and_then(|cb| #constructor) .unwrap_or_else(|e| { unsafe { napi::bindgen_prelude::JsError::from(e).throw_into(env) }; std::ptr::null_mut::() }) } } } fn gen_napi_value_map_impl(&self) -> TokenStream { match &self.kind { NapiStructKind::Class(class) if !class.ctor => gen_napi_value_map_impl( &self.name, self.gen_to_napi_value_ctor_impl_for_non_default_constructor_struct(class), self.has_lifetime, ), NapiStructKind::Class(class) => gen_napi_value_map_impl( &self.name, self.gen_to_napi_value_ctor_impl(class), self.has_lifetime, ), NapiStructKind::Object(obj) => self.gen_to_napi_value_obj_impl(obj), NapiStructKind::StructuredEnum(structured_enum) => { self.gen_to_napi_value_structured_enum_impl(structured_enum) } } } fn gen_to_napi_value_ctor_impl_for_non_default_constructor_struct( &self, class: &NapiClass, ) -> TokenStream { let name = &self.name; let js_name_raw = &self.js_name; let js_name_str = format!("{}\0", js_name_raw); let iterator_implementation = self.gen_iterator_property(class, name); let (object_finalize_impl, to_napi_value_impl, javascript_class_ext_impl) = if self.has_lifetime { let name = quote! { #name<'_javascript_function_scope> }; ( quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::ObjectFinalize for #name {} }, quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::ToNapiValue for #name }, quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::JavaScriptClassExt for #name }, ) } else { ( quote! { impl napi::bindgen_prelude::ObjectFinalize for #name {} }, quote! { impl napi::bindgen_prelude::ToNapiValue for #name }, quote! { impl napi::bindgen_prelude::JavaScriptClassExt for #name }, ) }; let finalize_trait = if class.use_custom_finalize { quote! {} } else { quote! { #[automatically_derived] #object_finalize_impl } }; quote! { #[automatically_derived] #to_napi_value_impl { unsafe fn to_napi_value( env: napi::sys::napi_env, val: #name ) -> napi::Result { if let Some(ctor_ref) = napi::__private::get_class_constructor(#js_name_str) { let mut wrapped_value = Box::into_raw(Box::new(val)); if wrapped_value as usize == 0x1 { wrapped_value = Box::into_raw(Box::new(0u8)).cast(); } let instance_value = napi::bindgen_prelude::new_instance::<#name>(env, wrapped_value.cast(), ctor_ref)?; #iterator_implementation Ok(instance_value) } else { Err(napi::bindgen_prelude::Error::new( napi::bindgen_prelude::Status::InvalidArg, format!("Failed to get constructor of class `{}` in `ToNapiValue`", #js_name_raw)) ) } } } #finalize_trait #[automatically_derived] #javascript_class_ext_impl { fn into_instance<'scope>(self, env: &'scope napi::Env) -> napi::Result> { if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) { unsafe { let wrapped_value = Box::into_raw(Box::new(self)); let instance_value = napi::bindgen_prelude::new_instance::<#name>(env.raw(), wrapped_value as *mut _ as *mut std::ffi::c_void, ctor_ref)?; Ok(napi::bindgen_prelude::ClassInstance::new(instance_value, env.raw(), wrapped_value)) } } else { Err(napi::bindgen_prelude::Error::new( napi::bindgen_prelude::Status::InvalidArg, format!("Failed to get constructor of class `{}`", #js_name_raw)) ) } } fn into_reference(self, env: napi::Env) -> napi::Result> { if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) { unsafe { let mut wrapped_value = Box::into_raw(Box::new(self)); if wrapped_value as usize == 0x1 { wrapped_value = Box::into_raw(Box::new(0u8)).cast(); } let instance_value = napi::bindgen_prelude::new_instance::<#name>(env.raw(), wrapped_value.cast(), ctor_ref)?; { let env = env.raw(); #iterator_implementation } napi::bindgen_prelude::Reference::<#name>::from_value_ptr(wrapped_value.cast(), env.raw()) } } else { Err(napi::bindgen_prelude::Error::new( napi::bindgen_prelude::Status::InvalidArg, format!("Failed to get constructor of class `{}`", #js_name_raw)) ) } } fn instance_of(env: napi::bindgen_prelude::Env, value: V) -> napi::bindgen_prelude::Result { if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) { let mut ctor = std::ptr::null_mut(); napi::check_status!( unsafe { napi::sys::napi_get_reference_value(env.raw(), ctor_ref, &mut ctor) }, "Failed to get constructor reference of class `{}`", #js_name_str )?; let mut is_instance_of = false; napi::check_status!( unsafe { napi::sys::napi_instanceof(env.raw(), value.raw(), ctor, &mut is_instance_of) }, "Failed to run instanceof for class `{}`", #js_name_str )?; Ok(is_instance_of) } else { Err(napi::Error::new(napi::Status::GenericFailure, format!("Failed to get constructor of class `{}`", #js_name_str))) } } } } } fn gen_iterator_property(&self, class: &NapiClass, name: &Ident) -> TokenStream { if !class.implement_iterator { return quote! {}; } quote! { napi::__private::create_iterator::<#name>(env, instance_value, wrapped_value); } } fn gen_to_napi_value_ctor_impl(&self, class: &NapiClass) -> TokenStream { let name = &self.name; let js_name_without_null = &self.js_name; let js_name_str = format!("{}\0", &self.js_name); let mut field_conversions = vec![]; let mut field_destructions = vec![]; for field in class.fields.iter() { let ty = &field.ty; match &field.name { syn::Member::Named(ident) => { // alias here prevents field name shadowing let alias_ident = format_ident!("{}_", ident); field_destructions.push(quote! { #ident: #alias_ident }); field_conversions.push( quote! { <#ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #alias_ident)? }, ); } syn::Member::Unnamed(i) => { let arg_name = format_ident!("arg{}", i); field_destructions.push(quote! { #arg_name }); field_conversions.push( quote! { <#ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #arg_name)? }, ); } } } let destructed_fields = if class.is_tuple { quote! { Self (#(#field_destructions),*) } } else { quote! { Self {#(#field_destructions),*} } }; let finalize_trait = if class.use_custom_finalize { quote! {} } else if self.has_lifetime { quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::ObjectFinalize for #name<'_javascript_function_scope> {} } } else { quote! { impl napi::bindgen_prelude::ObjectFinalize for #name {} } }; let to_napi_value_impl = if self.has_lifetime { quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::ToNapiValue for #name<'_javascript_function_scope> } } else { quote! { impl napi::bindgen_prelude::ToNapiValue for #name } }; quote! { #[automatically_derived] #to_napi_value_impl { unsafe fn to_napi_value( env: napi::bindgen_prelude::sys::napi_env, val: #name, ) -> napi::bindgen_prelude::Result { if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) { let mut ctor = std::ptr::null_mut(); napi::bindgen_prelude::check_status!( napi::bindgen_prelude::sys::napi_get_reference_value(env, ctor_ref, &mut ctor), "Failed to get constructor reference of class `{}`", #js_name_without_null )?; let mut instance_value = std::ptr::null_mut(); let #destructed_fields = val; let args = vec![#(#field_conversions),*]; napi::bindgen_prelude::check_status!( napi::bindgen_prelude::sys::napi_new_instance(env, ctor, args.len(), args.as_ptr(), &mut instance_value), "Failed to construct class `{}`", #js_name_without_null )?; Ok(instance_value) } else { Err(napi::bindgen_prelude::Error::new( napi::bindgen_prelude::Status::InvalidArg, format!("Failed to get constructor of class `{}`", #js_name_str)) ) } } } #finalize_trait } } fn gen_to_napi_value_obj_impl(&self, obj: &NapiObject) -> TokenStream { let name = &self.name; let name_str = self.name.to_string(); let mut obj_field_setters = vec![]; let mut obj_field_getters = vec![]; let mut field_destructions = vec![]; for field in obj.fields.iter() { let field_js_name = &field.js_name; let ty = &field.ty; let is_optional_field = if let syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) = &ty { if let Some(last_path) = segments.last() { last_path.ident == "Option" } else { false } } else { false }; match &field.name { syn::Member::Named(ident) => { let alias_ident = format_ident!("{}_", ident); field_destructions.push(quote! { #ident: #alias_ident }); if is_optional_field { obj_field_setters.push(match self.use_nullable { false => quote! { if #alias_ident.is_some() { obj.set(#field_js_name, #alias_ident)?; } }, true => quote! { if let Some(#alias_ident) = #alias_ident { obj.set(#field_js_name, #alias_ident)?; } else { obj.set(#field_js_name, napi::bindgen_prelude::Null)?; } }, }); } else { obj_field_setters.push(quote! { obj.set(#field_js_name, #alias_ident)?; }); } if is_optional_field && !self.use_nullable { obj_field_getters.push(quote! { let #alias_ident: #ty = obj.get(#field_js_name).map_err(|mut err| { err.reason = format!("{} on {}.{}", err.reason, #name_str, #field_js_name); err })?; }); } else { obj_field_getters.push(quote! { let #alias_ident: #ty = obj.get(#field_js_name).map_err(|mut err| { err.reason = format!("{} on {}.{}", err.reason, #name_str, #field_js_name); err })?.ok_or_else(|| napi::bindgen_prelude::Error::new( napi::bindgen_prelude::Status::InvalidArg, format!("Missing field `{}`", #field_js_name), ))?; }); } } syn::Member::Unnamed(i) => { let arg_name = format_ident!("arg{}", i); field_destructions.push(quote! { #arg_name }); if is_optional_field { obj_field_setters.push(match self.use_nullable { false => quote! { if #arg_name.is_some() { obj.set(#field_js_name, #arg_name)?; } }, true => quote! { if let Some(#arg_name) = #arg_name { obj.set(#field_js_name, #arg_name)?; } else { obj.set(#field_js_name, napi::bindgen_prelude::Null)?; } }, }); } else { obj_field_setters.push(quote! { obj.set(#field_js_name, #arg_name)?; }); } if is_optional_field && !self.use_nullable { obj_field_getters.push(quote! { let #arg_name: #ty = obj.get(#field_js_name)?; }); } else { obj_field_getters.push(quote! { let #arg_name: #ty = obj.get(#field_js_name)?.ok_or_else(|| napi::bindgen_prelude::Error::new( napi::bindgen_prelude::Status::InvalidArg, format!("Missing field `{}`", #field_js_name), ))?; }); } } } } let destructed_fields = if obj.is_tuple { quote! { Self (#(#field_destructions),*) } } else { quote! { Self {#(#field_destructions),*} } }; let name_with_lifetime = if self.has_lifetime { quote! { #name<'_javascript_function_scope> } } else { quote! { #name } }; let (from_napi_value_impl, to_napi_value_impl, validate_napi_value_impl, type_name_impl) = if self.has_lifetime { ( quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::FromNapiValue for #name<'_javascript_function_scope> }, quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::ToNapiValue for #name<'_javascript_function_scope> }, quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::ValidateNapiValue for #name<'_javascript_function_scope> }, quote! { impl <'_javascript_function_scope> napi::bindgen_prelude::TypeName for #name<'_javascript_function_scope> }, ) } else { ( quote! { impl napi::bindgen_prelude::FromNapiValue for #name }, quote! { impl napi::bindgen_prelude::ToNapiValue for #name }, quote! { impl napi::bindgen_prelude::ValidateNapiValue for #name }, quote! { impl napi::bindgen_prelude::TypeName for #name }, ) }; let to_napi_value = if obj.object_to_js { quote! { #[automatically_derived] #to_napi_value_impl { unsafe fn to_napi_value(env: napi::bindgen_prelude::sys::napi_env, val: #name_with_lifetime) -> napi::bindgen_prelude::Result { #[allow(unused_variables)] let env_wrapper = napi::bindgen_prelude::Env::from(env); #[allow(unused_mut)] let mut obj = env_wrapper.create_object()?; let #destructed_fields = val; #(#obj_field_setters)* napi::bindgen_prelude::Object::to_napi_value(env, obj) } } } } else { quote! {} }; let from_napi_value = if obj.object_from_js { let return_type = if self.has_lifetime { quote! { #name<'_javascript_function_scope> } } else { quote! { #name } }; quote! { #[automatically_derived] #from_napi_value_impl { unsafe fn from_napi_value( env: napi::bindgen_prelude::sys::napi_env, napi_val: napi::bindgen_prelude::sys::napi_value ) -> napi::bindgen_prelude::Result<#return_type> { #[allow(unused_variables)] let env_wrapper = napi::bindgen_prelude::Env::from(env); #[allow(unused_mut)] let mut obj = napi::bindgen_prelude::Object::from_napi_value(env, napi_val)?; #(#obj_field_getters)* let val = #destructed_fields; Ok(val) } } #[automatically_derived] #validate_napi_value_impl {} } } else { quote! {} }; quote! { #[automatically_derived] #type_name_impl { fn type_name() -> &'static str { #name_str } fn value_type() -> napi::ValueType { napi::ValueType::Object } } #to_napi_value #from_napi_value } } fn gen_default_getters_setters(&self, class: &NapiClass) -> Vec<(String, TokenStream)> { let mut getters_setters = vec![]; let struct_name = &self.name; for field in class.fields.iter() { let field_ident = &field.name; let field_name = match &field.name { syn::Member::Named(ident) => ident.to_string(), syn::Member::Unnamed(i) => format!("field{}", i.index), }; let ty = &field.ty; let getter_name = Ident::new( &format!("get_{}", rm_raw_prefix(&field_name)), Span::call_site(), ); let setter_name = Ident::new( &format!("set_{}", rm_raw_prefix(&field_name)), Span::call_site(), ); if field.getter { let default_to_napi_value_convert = quote! { let val = obj.#field_ident.to_owned(); unsafe { <#ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, val) } }; let to_napi_value_convert = if let syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) = ty { if let Some(syn::PathSegment { ident, .. }) = segments.last() { if TYPED_ARRAY_TYPE.iter().any(|name| ident == name) || ident == "Buffer" { quote! { let val = &mut obj.#field_ident; unsafe { <&mut #ty as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, val) } } } else { default_to_napi_value_convert } } else { default_to_napi_value_convert } } else { default_to_napi_value_convert }; getters_setters.push(( field.js_name.clone(), quote! { extern "C" fn #getter_name( env: napi::bindgen_prelude::sys::napi_env, cb: napi::bindgen_prelude::sys::napi_callback_info ) -> napi::bindgen_prelude::sys::napi_value { napi::bindgen_prelude::CallbackInfo::<0>::new(env, cb, Some(0), false) .and_then(|mut cb| unsafe { cb.unwrap_borrow_mut::<#struct_name>() }) .and_then(|obj| { #to_napi_value_convert }) .unwrap_or_else(|e| { unsafe { napi::bindgen_prelude::JsError::from(e).throw_into(env) }; std::ptr::null_mut::() }) } }, )); } if field.setter { getters_setters.push(( field.js_name.clone(), quote! { extern "C" fn #setter_name( env: napi::bindgen_prelude::sys::napi_env, cb: napi::bindgen_prelude::sys::napi_callback_info ) -> napi::bindgen_prelude::sys::napi_value { napi::bindgen_prelude::CallbackInfo::<1>::new(env, cb, Some(1), false) .and_then(|mut cb_info| unsafe { cb_info.unwrap_borrow_mut::<#struct_name>() .and_then(|obj| { <#ty as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb_info.get_arg(0)) .and_then(move |val| { obj.#field_ident = val; <() as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, ()) }) }) }) .unwrap_or_else(|e| { unsafe { napi::bindgen_prelude::JsError::from(e).throw_into(env) }; std::ptr::null_mut::() }) } }, )); } } getters_setters } fn gen_register(&self, class: &NapiClass) -> TokenStream { let name = &self.name; let struct_register_name = &self.register_name; let js_name = format!("{}\0", self.js_name); let mut props = vec![]; if class.ctor { props.push(quote! { napi::bindgen_prelude::Property::new("constructor").unwrap().with_ctor(constructor) }); } for field in class.fields.iter() { let field_name = match &field.name { syn::Member::Named(ident) => ident.to_string(), syn::Member::Unnamed(i) => format!("field{}", i.index), }; if !field.getter { continue; } let js_name = &field.js_name; let mut attribute = super::PROPERTY_ATTRIBUTE_DEFAULT; if field.writable { attribute |= super::PROPERTY_ATTRIBUTE_WRITABLE; } if field.enumerable { attribute |= super::PROPERTY_ATTRIBUTE_ENUMERABLE; } if field.configurable { attribute |= super::PROPERTY_ATTRIBUTE_CONFIGURABLE; } let mut prop = quote! { napi::bindgen_prelude::Property::new(#js_name) .unwrap() .with_property_attributes(napi::bindgen_prelude::PropertyAttributes::from_bits(#attribute).unwrap()) }; if field.getter { let getter_name = Ident::new( &format!("get_{}", rm_raw_prefix(&field_name)), Span::call_site(), ); (quote! { .with_getter(#getter_name) }).to_tokens(&mut prop); } if field.writable && field.setter { let setter_name = Ident::new( &format!("set_{}", rm_raw_prefix(&field_name)), Span::call_site(), ); (quote! { .with_setter(#setter_name) }).to_tokens(&mut prop); } props.push(prop); } let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref()); quote! { #[allow(non_snake_case)] #[allow(clippy::all)] #[cfg(all(not(test), not(target_family = "wasm")))] #[napi::bindgen_prelude::ctor] fn #struct_register_name() { napi::__private::register_class(std::any::TypeId::of::<#name>(), #js_mod_ident, #js_name, vec![#(#props),*]); } #[allow(non_snake_case)] #[allow(clippy::all)] #[cfg(all(not(test), target_family = "wasm"))] #[no_mangle] extern "C" fn #struct_register_name() { napi::__private::register_class(std::any::TypeId::of::<#name>(), #js_mod_ident, #js_name, vec![#(#props),*]); } } } fn gen_to_napi_value_structured_enum_impl( &self, structured_enum: &NapiStructuredEnum, ) -> TokenStream { let name = &self.name; let name_str = self.name.to_string(); let discriminant = structured_enum.discriminant.as_str(); let mut variant_arm_setters = vec![]; let mut variant_arm_getters = vec![]; for variant in structured_enum.variants.iter() { let variant_name = &variant.name; let variant_name_str = variant_name.to_string(); let mut obj_field_setters = vec![quote! { obj.set(#discriminant, #variant_name_str)?; }]; let mut obj_field_getters = vec![]; let mut field_destructions = vec![]; for field in variant.fields.iter() { let field_js_name = &field.js_name; let ty = &field.ty; let is_optional_field = if let syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) = &ty { if let Some(last_path) = segments.last() { last_path.ident == "Option" } else { false } } else { false }; match &field.name { syn::Member::Named(ident) => { let alias_ident = format_ident!("{}_", ident); field_destructions.push(quote! { #ident: #alias_ident }); if is_optional_field { obj_field_setters.push(match self.use_nullable { false => quote! { if #alias_ident.is_some() { obj.set(#field_js_name, #alias_ident)?; } }, true => quote! { if let Some(#alias_ident) = #alias_ident { obj.set(#field_js_name, #alias_ident)?; } else { obj.set(#field_js_name, napi::bindgen_prelude::Null)?; } }, }); } else { obj_field_setters.push(quote! { obj.set(#field_js_name, #alias_ident)?; }); } if is_optional_field && !self.use_nullable { obj_field_getters.push(quote! { let #alias_ident: #ty = obj.get(#field_js_name).map_err(|mut err| { err.reason = format!("{} on {}.{}", err.reason, #name_str, #field_js_name); err })?; }); } else { obj_field_getters.push(quote! { let #alias_ident: #ty = obj.get(#field_js_name).map_err(|mut err| { err.reason = format!("{} on {}.{}", err.reason, #name_str, #field_js_name); err })?.ok_or_else(|| napi::bindgen_prelude::Error::new( napi::bindgen_prelude::Status::InvalidArg, format!("Missing field `{}`", #field_js_name), ))?; }); } } syn::Member::Unnamed(i) => { let arg_name = format_ident!("arg{}", i); field_destructions.push(quote! { #arg_name }); if is_optional_field { obj_field_setters.push(match self.use_nullable { false => quote! { if #arg_name.is_some() { obj.set(#field_js_name, #arg_name)?; } }, true => quote! { if let Some(#arg_name) = #arg_name { obj.set(#field_js_name, #arg_name)?; } else { obj.set(#field_js_name, napi::bindgen_prelude::Null)?; } }, }); } else { obj_field_setters.push(quote! { obj.set(#field_js_name, #arg_name)?; }); } if is_optional_field && !self.use_nullable { obj_field_getters.push(quote! { let #arg_name: #ty = obj.get(#field_js_name)?; }); } else { obj_field_getters.push(quote! { let #arg_name: #ty = obj.get(#field_js_name)?.ok_or_else(|| napi::bindgen_prelude::Error::new( napi::bindgen_prelude::Status::InvalidArg, format!("Missing field `{}`", #field_js_name), ))?; }); } } } } let destructed_fields = if variant.is_tuple { quote! { Self::#variant_name (#(#field_destructions),*) } } else { quote! { Self::#variant_name {#(#field_destructions),*} } }; variant_arm_setters.push(quote! { #destructed_fields => { #(#obj_field_setters)* }, }); variant_arm_getters.push(quote! { #variant_name_str => { #(#obj_field_getters)* #destructed_fields }, }) } let to_napi_value = if structured_enum.object_to_js { quote! { impl napi::bindgen_prelude::ToNapiValue for #name { unsafe fn to_napi_value(env: napi::bindgen_prelude::sys::napi_env, val: #name) -> napi::bindgen_prelude::Result { #[allow(unused_variables)] let env_wrapper = napi::bindgen_prelude::Env::from(env); #[allow(unused_mut)] let mut obj = env_wrapper.create_object()?; match val { #(#variant_arm_setters)* }; napi::bindgen_prelude::Object::to_napi_value(env, obj) } } } } else { quote! {} }; let from_napi_value = if structured_enum.object_from_js { quote! { impl napi::bindgen_prelude::FromNapiValue for #name { unsafe fn from_napi_value( env: napi::bindgen_prelude::sys::napi_env, napi_val: napi::bindgen_prelude::sys::napi_value ) -> napi::bindgen_prelude::Result { #[allow(unused_variables)] let env_wrapper = napi::bindgen_prelude::Env::from(env); #[allow(unused_mut)] let mut obj = napi::bindgen_prelude::Object::from_napi_value(env, napi_val)?; let type_: String = obj.get(#discriminant).map_err(|mut err| { err.reason = format!("{} on {}.{}", err.reason, #name_str, #discriminant); err })?.ok_or_else(|| napi::bindgen_prelude::Error::new( napi::bindgen_prelude::Status::InvalidArg, format!("Missing field `{}`", #discriminant), ))?; let val = match type_.as_str() { #(#variant_arm_getters)* _ => return Err(napi::bindgen_prelude::Error::new( napi::bindgen_prelude::Status::InvalidArg, format!("Unknown variant `{}`", type_), )), }; Ok(val) } } impl napi::bindgen_prelude::ValidateNapiValue for #name {} } } else { quote! {} }; quote! { impl napi::bindgen_prelude::TypeName for #name { fn type_name() -> &'static str { #name_str } fn value_type() -> napi::ValueType { napi::ValueType::Object } } #to_napi_value #from_napi_value } } } impl TryToTokens for NapiImpl { fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> { self.gen_helper_mod()?.to_tokens(tokens); Ok(()) } } impl NapiImpl { fn gen_helper_mod(&self) -> BindgenResult { let name = &self.name; let name_str = self.name.to_string(); let js_name = format!("{}\0", self.js_name); let mod_name = Ident::new( &format!( "__napi_impl_helper_{}_{}", name_str, NAPI_IMPL_ID.fetch_add(1, Ordering::SeqCst) ), Span::call_site(), ); let register_name = &self.register_name; let mut methods = vec![]; let mut props = HashMap::new(); for item in self.items.iter() { let js_name = Literal::string(&item.js_name); let item_str = item.name.to_string(); let intermediate_name = get_intermediate_ident(&item_str); methods.push(item.try_to_token_stream()?); let mut attribute = super::PROPERTY_ATTRIBUTE_DEFAULT; if item.writable { attribute |= super::PROPERTY_ATTRIBUTE_WRITABLE; } if item.enumerable { attribute |= super::PROPERTY_ATTRIBUTE_ENUMERABLE; } if item.configurable { attribute |= super::PROPERTY_ATTRIBUTE_CONFIGURABLE; } let prop = props.entry(&item.js_name).or_insert_with(|| { quote! { napi::bindgen_prelude::Property::new(#js_name).unwrap().with_property_attributes(napi::bindgen_prelude::PropertyAttributes::from_bits(#attribute).unwrap()) } }); let appendix = match item.kind { FnKind::Constructor => quote! { .with_ctor(#intermediate_name) }, FnKind::Getter => quote! { .with_getter(#intermediate_name) }, FnKind::Setter => quote! { .with_setter(#intermediate_name) }, _ => { if item.fn_self.is_some() { quote! { .with_method(#intermediate_name) } } else { quote! { .with_method(#intermediate_name).with_property_attributes(napi::bindgen_prelude::PropertyAttributes::Static) } } } }; appendix.to_tokens(prop); } let mut props: Vec<_> = props.into_iter().collect(); props.sort_by_key(|(_, prop)| prop.to_string()); let props = props.into_iter().map(|(_, prop)| prop); let props_wasm = props.clone(); let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref()); Ok(quote! { #[allow(non_snake_case)] #[allow(clippy::all)] mod #mod_name { use super::*; #(#methods)* #[cfg(all(not(test), not(target_family = "wasm")))] #[napi::bindgen_prelude::ctor] fn #register_name() { napi::__private::register_class(std::any::TypeId::of::<#name>(), #js_mod_ident, #js_name, vec![#(#props),*]); } #[cfg(all(not(test), target_family = "wasm"))] #[no_mangle] extern "C" fn #register_name() { napi::__private::register_class(std::any::TypeId::of::<#name>(), #js_mod_ident, #js_name, vec![#(#props_wasm),*]); } } }) } } pub fn rm_raw_prefix(s: &str) -> &str { if let Some(stripped) = s.strip_prefix("r#") { stripped } else { s } }