mirror of
https://github.com/napi-rs/napi-rs.git
synced 2025-12-08 19:56:07 +00:00
1205 lines
40 KiB
Rust
1205 lines
40 KiB
Rust
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<napi::sys::napi_value> {
|
|
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<Self> {
|
|
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<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::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::<napi::bindgen_prelude::sys::napi_value__>()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
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<napi::bindgen_prelude::sys::napi_value> {
|
|
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<napi::bindgen_prelude::ClassInstance<'scope, Self>>
|
|
{
|
|
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<napi::bindgen_prelude::Reference<Self>> {
|
|
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<V: napi::NapiRaw>(env: napi::bindgen_prelude::Env, value: V) -> napi::bindgen_prelude::Result<bool> {
|
|
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<napi::bindgen_prelude::sys::napi_value> {
|
|
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<napi::bindgen_prelude::sys::napi_value> {
|
|
#[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::<napi::bindgen_prelude::sys::napi_value__>()
|
|
})
|
|
}
|
|
},
|
|
));
|
|
}
|
|
|
|
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::<napi::bindgen_prelude::sys::napi_value__>()
|
|
})
|
|
}
|
|
},
|
|
));
|
|
}
|
|
}
|
|
|
|
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<napi::bindgen_prelude::sys::napi_value> {
|
|
#[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<Self> {
|
|
#[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<TokenStream> {
|
|
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
|
|
}
|
|
}
|