feat(napi): experimental iterator support

This commit is contained in:
LongYinan 2022-05-06 17:40:46 +08:00
parent b074608582
commit a3356264f2
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
24 changed files with 947 additions and 102 deletions

View file

@ -21,6 +21,7 @@ pub struct NapiFn {
pub ts_return_type: Option<String>, pub ts_return_type: Option<String>,
pub skip_typescript: bool, pub skip_typescript: bool,
pub comments: Vec<String>, pub comments: Vec<String>,
pub parent_is_generator: bool,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -62,6 +63,7 @@ pub struct NapiStruct {
pub kind: NapiStructKind, pub kind: NapiStructKind,
pub js_mod: Option<String>, pub js_mod: Option<String>,
pub comments: Vec<String>, pub comments: Vec<String>,
pub implement_iterator: bool,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -89,6 +91,9 @@ pub struct NapiImpl {
pub js_name: String, pub js_name: String,
pub items: Vec<NapiFn>, pub items: Vec<NapiFn>,
pub task_output_type: Option<Type>, pub task_output_type: Option<Type>,
pub iterator_yield_type: Option<Type>,
pub iterator_next_type: Option<Type>,
pub iterator_return_type: Option<Type>,
pub js_mod: Option<String>, pub js_mod: Option<String>,
pub comments: Vec<String>, pub comments: Vec<String>,
} }

View file

@ -128,7 +128,7 @@ impl NapiFn {
} else { } else {
if self.parent.is_some() { if self.parent.is_some() {
if let syn::Type::Path(path) = path.ty.as_ref() { if let syn::Type::Path(path) = path.ty.as_ref() {
if let Some(p) = path.path.segments.first() { if let Some(p) = path.path.segments.last() {
if p.ident == "Reference" { if p.ident == "Reference" {
if let syn::PathArguments::AngleBracketed( if let syn::PathArguments::AngleBracketed(
syn::AngleBracketedGenericArguments { args: angle_bracketed_args, .. }, syn::AngleBracketedGenericArguments { args: angle_bracketed_args, .. },
@ -281,7 +281,13 @@ impl NapiFn {
let is_return_self = ty_string == "& Self" || ty_string == "&mut Self"; let is_return_self = ty_string == "& Self" || ty_string == "&mut Self";
if self.kind == FnKind::Constructor { if self.kind == FnKind::Constructor {
if self.is_ret_result { if self.is_ret_result {
if self.parent_is_generator {
quote! { cb.construct_generator(#js_name, #ret?) }
} else {
quote! { cb.construct(#js_name, #ret?) } quote! { cb.construct(#js_name, #ret?) }
}
} else if self.parent_is_generator {
quote! { cb.construct_generator(#js_name, #ret) }
} else { } else {
quote! { cb.construct(#js_name, #ret) } quote! { cb.construct(#js_name, #ret) }
} }

View file

@ -186,13 +186,19 @@ impl NapiStruct {
quote! { #name {#(#fields),*} } quote! { #name {#(#fields),*} }
}; };
let constructor = if self.implement_iterator {
quote! { unsafe { cb.construct_generator(#js_name_str, #construct) } }
} else {
quote! { unsafe { cb.construct(#js_name_str, #construct) } }
};
quote! { quote! {
extern "C" fn constructor( extern "C" fn constructor(
env: napi::bindgen_prelude::sys::napi_env, env: napi::bindgen_prelude::sys::napi_env,
cb: napi::bindgen_prelude::sys::napi_callback_info cb: napi::bindgen_prelude::sys::napi_callback_info
) -> napi::bindgen_prelude::sys::napi_value { ) -> napi::bindgen_prelude::sys::napi_value {
napi::bindgen_prelude::CallbackInfo::<#fields_len>::new(env, cb, None) napi::bindgen_prelude::CallbackInfo::<#fields_len>::new(env, cb, None)
.and_then(|cb| unsafe { cb.construct(#js_name_str, #construct) }) .and_then(|cb| #constructor)
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
unsafe { napi::bindgen_prelude::JsError::from(e).throw_into(env) }; unsafe { napi::bindgen_prelude::JsError::from(e).throw_into(env) };
std::ptr::null_mut::<napi::bindgen_prelude::sys::napi_value__>() std::ptr::null_mut::<napi::bindgen_prelude::sys::napi_value__>()
@ -218,18 +224,21 @@ impl NapiStruct {
let name = &self.name; let name = &self.name;
let js_name_raw = &self.js_name; let js_name_raw = &self.js_name;
let js_name_str = format!("{}\0", js_name_raw); let js_name_str = format!("{}\0", js_name_raw);
let iterator_implementation = self.gen_iterator_property(name);
quote! { quote! {
impl napi::bindgen_prelude::ToNapiValue for #name { impl napi::bindgen_prelude::ToNapiValue for #name {
unsafe fn to_napi_value( unsafe fn to_napi_value(
env: napi::sys::napi_env, env: napi::sys::napi_env,
val: #name val: #name
) -> napi::Result<napi::bindgen_prelude::sys::napi_value> { ) -> napi::Result<napi::bindgen_prelude::sys::napi_value> {
if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) { if let Some(ctor_ref) = napi::__private::get_class_constructor(#js_name_str) {
let wrapped_value = Box::into_raw(Box::new(val)) as *mut std::ffi::c_void; let wrapped_value = Box::into_raw(Box::new(val));
#name::new_instance(env, wrapped_value, ctor_ref) let instance_value = #name::new_instance(env, wrapped_value as *mut std::ffi::c_void, ctor_ref)?;
#iterator_implementation
Ok(instance_value)
} else { } else {
Err(napi::bindgen_prelude::Error::new( Err(napi::bindgen_prelude::Error::new(
napi::bindgen_prelude::Status::InvalidArg, format!("Failed to get constructor of class `{}`", #js_name_raw)) napi::bindgen_prelude::Status::InvalidArg, format!("Failed to get constructor of class `{}` in `ToNapiValue`", #js_name_raw))
) )
} }
} }
@ -239,9 +248,13 @@ impl NapiStruct {
pub fn into_reference(val: #name, env: napi::Env) -> napi::Result<napi::bindgen_prelude::Reference<#name>> { pub fn into_reference(val: #name, env: napi::Env) -> napi::Result<napi::bindgen_prelude::Reference<#name>> {
if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) { if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) {
unsafe { unsafe {
let wrapped_value = Box::into_raw(Box::new(val)) as *mut std::ffi::c_void; let wrapped_value = Box::into_raw(Box::new(val));
#name::new_instance(env.raw(), wrapped_value, ctor_ref)?; let instance_value = #name::new_instance(env.raw(), wrapped_value as *mut std::ffi::c_void, ctor_ref)?;
napi::bindgen_prelude::Reference::<#name>::from_value_ptr(wrapped_value, env.raw()) {
let env = env.raw();
#iterator_implementation
}
napi::bindgen_prelude::Reference::<#name>::from_value_ptr(wrapped_value as *mut std::ffi::c_void, env.raw())
} }
} else { } else {
Err(napi::bindgen_prelude::Error::new( Err(napi::bindgen_prelude::Error::new(
@ -258,7 +271,7 @@ impl NapiStruct {
let mut ctor = std::ptr::null_mut(); let mut ctor = std::ptr::null_mut();
napi::check_status!( napi::check_status!(
napi::sys::napi_get_reference_value(env, ctor_ref, &mut ctor), napi::sys::napi_get_reference_value(env, ctor_ref, &mut ctor),
"Failed to get constructor of class `{}`", "Failed to get constructor reference of class `{}`",
#js_name_raw #js_name_raw
)?; )?;
@ -292,6 +305,15 @@ impl NapiStruct {
} }
} }
fn gen_iterator_property(&self, name: &Ident) -> TokenStream {
if !self.implement_iterator {
return quote! {};
}
quote! {
napi::__private::create_iterator::<#name>(env, instance_value, wrapped_value);
}
}
fn gen_to_napi_value_ctor_impl(&self) -> TokenStream { fn gen_to_napi_value_ctor_impl(&self) -> TokenStream {
let name = &self.name; let name = &self.name;
let js_name_str = format!("{}\0", &self.js_name); let js_name_str = format!("{}\0", &self.js_name);
@ -331,28 +353,29 @@ impl NapiStruct {
quote! { quote! {
impl napi::bindgen_prelude::ToNapiValue for #name { impl napi::bindgen_prelude::ToNapiValue for #name {
unsafe fn to_napi_value( unsafe fn to_napi_value(
env: napi::bindgen_prelude::sys::napi_env, val: #name env: napi::bindgen_prelude::sys::napi_env,
val: #name,
) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> { ) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) { if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) {
let mut ctor = std::ptr::null_mut(); let mut ctor = std::ptr::null_mut();
napi::bindgen_prelude::check_status!( napi::bindgen_prelude::check_status!(
napi::bindgen_prelude::sys::napi_get_reference_value(env, ctor_ref, &mut ctor), napi::bindgen_prelude::sys::napi_get_reference_value(env, ctor_ref, &mut ctor),
"Failed to get constructor of class `{}`", "Failed to get constructor reference of class `{}`",
#js_name_str #js_name_str
)?; )?;
let mut result = std::ptr::null_mut(); let mut instance_value = std::ptr::null_mut();
let #destructed_fields = val; let #destructed_fields = val;
let args = vec![#(#field_conversions),*]; let args = vec![#(#field_conversions),*];
napi::bindgen_prelude::check_status!( napi::bindgen_prelude::check_status!(
napi::bindgen_prelude::sys::napi_new_instance(env, ctor, args.len(), args.as_ptr(), &mut result), napi::bindgen_prelude::sys::napi_new_instance(env, ctor, args.len(), args.as_ptr(), &mut instance_value),
"Failed to construct class `{}`", "Failed to construct class `{}`",
#js_name_str #js_name_str
)?; )?;
Ok(result) Ok(instance_value)
} else { } else {
Err(napi::bindgen_prelude::Error::new( Err(napi::bindgen_prelude::Error::new(
napi::bindgen_prelude::Status::InvalidArg, format!("Failed to get constructor of class `{}`", #js_name_str)) napi::bindgen_prelude::Status::InvalidArg, format!("Failed to get constructor of class `{}`", #js_name_str))
@ -601,7 +624,7 @@ impl NapiStruct {
#[cfg(all(not(test), not(feature = "noop")))] #[cfg(all(not(test), not(feature = "noop")))]
#[napi::bindgen_prelude::ctor] #[napi::bindgen_prelude::ctor]
fn #struct_register_name() { fn #struct_register_name() {
napi::bindgen_prelude::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props),*]); napi::__private::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props),*]);
} }
} }
} }
@ -675,7 +698,7 @@ impl NapiImpl {
#[cfg(all(not(test), not(feature = "noop")))] #[cfg(all(not(test), not(feature = "noop")))]
#[napi::bindgen_prelude::ctor] #[napi::bindgen_prelude::ctor]
fn #register_name() { fn #register_name() {
napi::bindgen_prelude::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props),*]); napi::__private::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props),*]);
} }
} }
}) })

View file

@ -24,6 +24,7 @@ pub struct Napi {
macro_rules! napi_ast_impl { macro_rules! napi_ast_impl {
( $( ($v:ident, $ast:ident), )* ) => { ( $( ($v:ident, $ast:ident), )* ) => {
#[derive(Debug)] #[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum NapiItem { pub enum NapiItem {
$($v($ast)),* $($v($ast)),*
} }

View file

@ -179,7 +179,6 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
("Either3", "{} | {} | {}"), ("Either3", "{} | {} | {}"),
("Either4", "{} | {} | {} | {}"), ("Either4", "{} | {} | {} | {}"),
("Either5", "{} | {} | {} | {} | {}"), ("Either5", "{} | {} | {} | {} | {}"),
("unknown", "unknown"),
("Null", "null"), ("Null", "null"),
("JsNull", "null"), ("JsNull", "null"),
("null", "null"), ("null", "null"),
@ -190,6 +189,8 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
("JsFunction", "(...args: any[]) => any"), ("JsFunction", "(...args: any[]) => any"),
("JsGlobal", "typeof global"), ("JsGlobal", "typeof global"),
("External", "ExternalObject<{}>"), ("External", "ExternalObject<{}>"),
("unknown", "unknown"),
("Unknown", "unknown"),
("JsUnknown", "unknown"), ("JsUnknown", "unknown"),
]); ]);
@ -281,12 +282,18 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool, is_struct_field: bool) -> (S
}); });
} else if rust_ty == "Reference" { } else if rust_ty == "Reference" {
ts_ty = r#struct::TASK_STRUCTS.with(|t| { ts_ty = r#struct::TASK_STRUCTS.with(|t| {
let (output_type, _) = args.first().unwrap().to_owned(); // Reference<T> => T
if let Some(arg) = args.first() {
let (output_type, _) = arg.to_owned();
if let Some(o) = t.borrow().get(&output_type) { if let Some(o) = t.borrow().get(&output_type) {
Some((o.to_owned(), false)) Some((o.to_owned(), false))
} else { } else {
Some((output_type, false)) Some((output_type, false))
} }
} else {
// Not NAPI-RS `Reference`
Some((rust_ty, false))
}
}); });
} else if let Some(&known_ty) = KNOWN_TYPES.get(rust_ty.as_str()) { } else if let Some(&known_ty) = KNOWN_TYPES.get(rust_ty.as_str()) {
if known_ty.contains("{}") { if known_ty.contains("{}") {

View file

@ -1,7 +1,7 @@
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
use quote::ToTokens; use quote::ToTokens;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use syn::Pat; use syn::{Pat, PathArguments, PathSegment};
use super::{ty_to_ts_type, ToTypeDef, TypeDef}; use super::{ty_to_ts_type, ToTypeDef, TypeDef};
use crate::{js_doc_from_comments, CallbackArg, FnKind, NapiFn}; use crate::{js_doc_from_comments, CallbackArg, FnKind, NapiFn};
@ -122,9 +122,21 @@ impl NapiFn {
.filter_map(|arg| match arg { .filter_map(|arg| match arg {
crate::NapiFnArgKind::PatType(path) => { crate::NapiFnArgKind::PatType(path) => {
let ty_string = path.ty.to_token_stream().to_string(); let ty_string = path.ty.to_token_stream().to_string();
if ty_string == "Env" || ty_string.replace(' ', "").starts_with("Reference<") { if ty_string == "Env" {
return None; return None;
} }
if let syn::Type::Path(path) = path.ty.as_ref() {
if let Some(PathSegment {
ident,
arguments: PathArguments::AngleBracketed(_),
}) = path.path.segments.last()
{
if ident == "Reference" {
return None;
}
}
}
let mut path = path.clone(); let mut path = path.clone();
// remove mutability from PatIdent // remove mutability from PatIdent
if let Pat::Ident(i) = path.pat.as_mut() { if let Pat::Ident(i) = path.pat.as_mut() {

View file

@ -43,6 +43,31 @@ impl ToTypeDef for NapiImpl {
}); });
} }
if let Some(output_type) = &self.iterator_yield_type {
let next_type = if let Some(ref ty) = self.iterator_next_type {
ty_to_ts_type(ty, false, false).0
} else {
"void".to_owned()
};
let return_type = if let Some(ref ty) = self.iterator_return_type {
ty_to_ts_type(ty, false, false).0
} else {
"void".to_owned()
};
Some(TypeDef {
kind: "impl".to_owned(),
name: self.js_name.to_owned(),
original_name: None,
def: format!(
"[Symbol.iterator](): Iterator<{}, {}, {}>",
ty_to_ts_type(output_type, false, true).0,
return_type,
next_type,
),
js_mod: self.js_mod.to_owned(),
js_doc: "".to_string(),
})
} else {
Some(TypeDef { Some(TypeDef {
kind: "impl".to_owned(), kind: "impl".to_owned(),
name: self.js_name.to_owned(), name: self.js_name.to_owned(),
@ -68,6 +93,7 @@ impl ToTypeDef for NapiImpl {
js_doc: "".to_string(), js_doc: "".to_string(),
}) })
} }
}
} }
impl NapiStruct { impl NapiStruct {

View file

@ -52,6 +52,7 @@ macro_rules! attrgen {
(strict, Strict(Span)), (strict, Strict(Span)),
(object, Object(Span)), (object, Object(Span)),
(namespace, Namespace(Span, String, Span)), (namespace, Namespace(Span, String, Span)),
(iterator, Iterator(Span)),
(ts_args_type, TsArgsType(Span, String, Span)), (ts_args_type, TsArgsType(Span, String, Span)),
(ts_return_type, TsReturnType(Span, String, Span)), (ts_return_type, TsReturnType(Span, String, Span)),
(ts_type, TsType(Span, String, Span)), (ts_type, TsType(Span, String, Span)),

View file

@ -1,7 +1,7 @@
#[macro_use] #[macro_use]
pub mod attrs; pub mod attrs;
use std::cell::Cell; use std::cell::{Cell, RefCell};
use std::collections::HashMap; use std::collections::HashMap;
use std::str::Chars; use std::str::Chars;
@ -17,10 +17,14 @@ use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::ToTokens; use quote::ToTokens;
use syn::ext::IdentExt; use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream, Result as SynResult}; use syn::parse::{Parse, ParseStream, Result as SynResult};
use syn::{Attribute, Signature, Type, Visibility}; use syn::{Attribute, PathSegment, Signature, Type, Visibility};
use crate::parser::attrs::{check_recorded_struct_for_impl, record_struct}; use crate::parser::attrs::{check_recorded_struct_for_impl, record_struct};
thread_local! {
static GENERATOR_STRUCT: RefCell<HashMap<String, bool>> = Default::default();
}
struct AnyIdent(Ident); struct AnyIdent(Ident);
impl Parse for AnyIdent { impl Parse for AnyIdent {
@ -562,6 +566,20 @@ fn napi_fn_from_decl(
) )
}; };
let namespace = opts.namespace().map(|(m, _)| m.to_owned());
let parent_is_generator = if let Some(p) = parent {
GENERATOR_STRUCT.with(|inner| {
let inner = inner.borrow();
let key = namespace
.as_ref()
.map(|n| format!("{}::{}", n, p))
.unwrap_or_else(|| p.to_string());
*inner.get(&key).unwrap_or(&false)
})
} else {
false
};
NapiFn { NapiFn {
name: ident, name: ident,
js_name, js_name,
@ -581,6 +599,7 @@ fn napi_fn_from_decl(
ts_args_type: opts.ts_args_type().map(|(m, _)| m.to_owned()), ts_args_type: opts.ts_args_type().map(|(m, _)| m.to_owned()),
ts_return_type: opts.ts_return_type().map(|(m, _)| m.to_owned()), ts_return_type: opts.ts_return_type().map(|(m, _)| m.to_owned()),
skip_typescript: opts.skip_typescript().is_some(), skip_typescript: opts.skip_typescript().is_some(),
parent_is_generator,
} }
}) })
} }
@ -786,6 +805,16 @@ impl ConvertToAST for syn::ItemStruct {
} }
record_struct(&struct_name, js_name.clone(), &opts); record_struct(&struct_name, js_name.clone(), &opts);
let namespace = opts.namespace().map(|(m, _)| m.to_owned());
let implement_iterator = opts.iterator().is_some();
GENERATOR_STRUCT.with(|inner| {
let mut inner = inner.borrow_mut();
let key = namespace
.as_ref()
.map(|n| format!("{}::{}", n, struct_name))
.unwrap_or_else(|| struct_name.to_string());
inner.insert(key, implement_iterator);
});
Diagnostic::from_vec(errors).map(|()| Napi { Diagnostic::from_vec(errors).map(|()| Napi {
item: NapiItem::Struct(NapiStruct { item: NapiItem::Struct(NapiStruct {
@ -795,8 +824,9 @@ impl ConvertToAST for syn::ItemStruct {
fields, fields,
is_tuple, is_tuple,
kind: struct_kind, kind: struct_kind,
js_mod: opts.namespace().map(|(m, _)| m.to_owned()), js_mod: namespace,
comments: extract_doc_comments(&self.attrs), comments: extract_doc_comments(&self.attrs),
implement_iterator,
}), }),
}) })
} }
@ -819,14 +849,31 @@ impl ConvertToAST for syn::ItemImpl {
let mut struct_js_name = struct_name.to_string().to_case(Case::UpperCamel); let mut struct_js_name = struct_name.to_string().to_case(Case::UpperCamel);
let mut items = vec![]; let mut items = vec![];
let mut task_output_type = None; let mut task_output_type = None;
let mut iterator_yield_type = None;
let mut iterator_next_type = None;
let mut iterator_return_type = None;
for item in self.items.iter_mut() { for item in self.items.iter_mut() {
if let Some(method) = match item { if let Some(method) = match item {
syn::ImplItem::Method(m) => Some(m), syn::ImplItem::Method(m) => Some(m),
syn::ImplItem::Type(m) => { syn::ImplItem::Type(m) => {
if m.ident == *"JsValue" { if let Some((_, t, _)) = &self.trait_ {
if let Some(PathSegment { ident, .. }) = t.segments.last() {
if ident == "Task" && m.ident == "JsValue" {
if let Type::Path(_) = &m.ty { if let Type::Path(_) = &m.ty {
task_output_type = Some(m.ty.clone()); task_output_type = Some(m.ty.clone());
} }
} else if ident == "Generator" {
if let Type::Path(_) = &m.ty {
if m.ident == "Yield" {
iterator_yield_type = Some(m.ty.clone());
} else if m.ident == "Next" {
iterator_next_type = Some(m.ty.clone());
} else if m.ident == "Return" {
iterator_return_type = Some(m.ty.clone());
}
}
}
}
} }
None None
} }
@ -866,13 +913,18 @@ impl ConvertToAST for syn::ItemImpl {
} }
} }
let namespace = impl_opts.namespace().map(|(m, _)| m.to_owned());
Ok(Napi { Ok(Napi {
item: NapiItem::Impl(NapiImpl { item: NapiItem::Impl(NapiImpl {
name: struct_name, name: struct_name,
js_name: struct_js_name, js_name: struct_js_name,
items, items,
task_output_type, task_output_type,
js_mod: impl_opts.namespace().map(|(m, _)| m.to_owned()), iterator_yield_type,
iterator_next_type,
iterator_return_type,
js_mod: namespace,
comments: extract_doc_comments(&self.attrs), comments: extract_doc_comments(&self.attrs),
}), }),
}) })

View file

@ -66,10 +66,10 @@ impl<const N: usize> CallbackInfo<N> {
self.this self.this
} }
pub fn construct<T: 'static>(&self, js_name: &str, obj: T) -> Result<sys::napi_value> { fn _construct<T: 'static>(&self, js_name: &str, obj: T) -> Result<(sys::napi_value, *mut T)> {
let obj = Box::new(obj); let obj = Box::new(obj);
let this = self.this(); let this = self.this();
let value_ref = Box::into_raw(obj) as *mut c_void; let value_ref = Box::into_raw(obj);
let mut object_ref = ptr::null_mut(); let mut object_ref = ptr::null_mut();
let initial_finalize: Box<dyn FnOnce()> = Box::new(|| {}); let initial_finalize: Box<dyn FnOnce()> = Box::new(|| {});
let finalize_callbacks_ptr = Rc::into_raw(Rc::new(Cell::new(Box::into_raw(initial_finalize)))); let finalize_callbacks_ptr = Rc::into_raw(Rc::new(Cell::new(Box::into_raw(initial_finalize))));
@ -78,7 +78,7 @@ impl<const N: usize> CallbackInfo<N> {
sys::napi_wrap( sys::napi_wrap(
self.env, self.env,
this, this,
value_ref, value_ref as *mut c_void,
Some(raw_finalize_unchecked::<T>), Some(raw_finalize_unchecked::<T>),
ptr::null_mut(), ptr::null_mut(),
&mut object_ref &mut object_ref
@ -88,8 +88,25 @@ impl<const N: usize> CallbackInfo<N> {
)?; )?;
}; };
Reference::<T>::add_ref(value_ref, (value_ref, object_ref, finalize_callbacks_ptr)); Reference::<T>::add_ref(
Ok(this) value_ref as *mut c_void,
(value_ref as *mut c_void, object_ref, finalize_callbacks_ptr),
);
Ok((this, value_ref))
}
pub fn construct<T: 'static>(&self, js_name: &str, obj: T) -> Result<sys::napi_value> {
self._construct(js_name, obj).map(|(v, _)| v)
}
pub fn construct_generator<T: Generator + 'static>(
&self,
js_name: &str,
obj: T,
) -> Result<sys::napi_value> {
let (instance, generator_ptr) = self._construct(js_name, obj)?;
crate::__private::create_iterator(self.env, instance, generator_ptr);
Ok(instance)
} }
pub fn factory<T: 'static>(&self, js_name: &str, obj: T) -> Result<sys::napi_value> { pub fn factory<T: 'static>(&self, js_name: &str, obj: T) -> Result<sys::napi_value> {

View file

@ -5,7 +5,6 @@ macro_rules! check_status_or_throw {
if let Err(e) = $crate::check_status!($code, $($msg)*) { if let Err(e) = $crate::check_status!($code, $($msg)*) {
#[allow(unused_unsafe)] #[allow(unused_unsafe)]
unsafe { $crate::JsError::from(e).throw_into($env) }; unsafe { $crate::JsError::from(e).throw_into($env) };
return;
} }
}; };
} }

View file

@ -0,0 +1,581 @@
use std::ptr;
use std::{ffi::c_void, os::raw::c_char};
use crate::Value;
use crate::{bindgen_runtime::Unknown, check_status_or_throw, sys, Env};
use super::{FromNapiValue, ToNapiValue};
const GENERATOR_STATE_KEY: &str = "[[GeneratorState]]\0";
/// Implement a Iterator for the JavaScript Class.
/// This feature is an experimental feature and is not yet stable.
pub trait Generator {
type Yield: ToNapiValue;
type Next: FromNapiValue;
type Return: FromNapiValue;
/// Handle the `Generator.next()`
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next
fn next(&mut self, value: Option<Self::Next>) -> Option<Self::Yield>;
#[allow(unused_variables)]
/// Implement complete to handle the `Generator.return()`
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/return
fn complete(&mut self, value: Option<Self::Return>) -> Option<Self::Yield> {
None
}
#[allow(unused_variables)]
/// Implement catch to handle the `Generator.throw()`
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/throw
fn catch(&mut self, env: Env, value: Unknown) -> Result<Option<Self::Yield>, Unknown> {
Err(value)
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn create_iterator<T: Generator>(
env: sys::napi_env,
instance: sys::napi_value,
generator_ptr: *mut T,
) {
let mut global = ptr::null_mut();
check_status_or_throw!(
env,
unsafe { sys::napi_get_global(env, &mut global) },
"Get global object failed",
);
let mut symbol_object = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_get_named_property(
env,
global,
"Symbol\0".as_ptr() as *const c_char,
&mut symbol_object,
)
},
"Get global object failed",
);
let mut iterator_symbol = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_get_named_property(
env,
symbol_object,
"iterator\0".as_ptr() as *const c_char,
&mut iterator_symbol,
)
},
"Get Symbol.iterator failed",
);
let mut generator_function = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_create_function(
env,
"Iterator\0".as_ptr() as *const c_char,
8,
Some(symbol_generator::<T>),
generator_ptr as *mut c_void,
&mut generator_function,
)
},
"Create iterator function failed",
);
check_status_or_throw!(
env,
unsafe { sys::napi_set_property(env, instance, iterator_symbol, generator_function) },
"Failed to set Symbol.iterator on class instance",
);
}
#[doc(hidden)]
pub unsafe extern "C" fn symbol_generator<T: Generator>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> sys::napi_value {
let mut this = ptr::null_mut();
let mut argv: [sys::napi_value; 1] = [ptr::null_mut()];
let mut argc = 0;
let mut generator_ptr = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_get_cb_info(
env,
info,
&mut argc,
argv.as_mut_ptr(),
&mut this,
&mut generator_ptr,
)
},
"Get callback info from generator function failed"
);
let mut generator_object = ptr::null_mut();
check_status_or_throw!(
env,
unsafe { sys::napi_create_object(env, &mut generator_object) },
"Create Generator object failed"
);
let mut next_function = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_create_function(
env,
"next\0".as_ptr() as *const c_char,
4,
Some(generator_next::<T>),
generator_ptr,
&mut next_function,
)
},
"Create next function failed"
);
let mut return_function = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_create_function(
env,
"return\0".as_ptr() as *const c_char,
6,
Some(generator_return::<T>),
generator_ptr,
&mut return_function,
)
},
"Create next function failed"
);
let mut throw_function = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_create_function(
env,
"throw\0".as_ptr() as *const c_char,
5,
Some(generator_throw::<T>),
generator_ptr,
&mut throw_function,
)
},
"Create next function failed"
);
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
generator_object,
"next\0".as_ptr() as *const c_char,
next_function,
)
},
"Set next function on Generator object failed"
);
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
generator_object,
"return\0".as_ptr() as *const c_char,
return_function,
)
},
"Set return function on Generator object failed"
);
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
generator_object,
"throw\0".as_ptr() as *const c_char,
throw_function,
)
},
"Set throw function on Generator object failed"
);
let mut generator_state = ptr::null_mut();
check_status_or_throw!(
env,
unsafe { sys::napi_get_boolean(env, false, &mut generator_state) },
"Create generator state failed"
);
let properties = vec![sys::napi_property_descriptor {
utf8name: GENERATOR_STATE_KEY.as_ptr() as *const c_char,
name: ptr::null_mut(),
method: None,
getter: None,
setter: None,
value: generator_state,
attributes: sys::PropertyAttributes::writable,
data: ptr::null_mut(),
}];
check_status_or_throw!(
env,
unsafe { sys::napi_define_properties(env, generator_object, 1, properties.as_ptr()) },
"Define properties on Generator object failed"
);
generator_object
}
extern "C" fn generator_next<T: Generator>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> sys::napi_value {
let mut this = ptr::null_mut();
let mut argv: [sys::napi_value; 1] = [ptr::null_mut()];
let mut argc = 1;
let mut generator_ptr = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_get_cb_info(
env,
info,
&mut argc,
argv.as_mut_ptr(),
&mut this,
&mut generator_ptr,
)
},
"Get callback info from generator function failed"
);
let mut generator_state = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_get_named_property(
env,
this,
GENERATOR_STATE_KEY.as_ptr() as *const c_char,
&mut generator_state,
)
},
"Get generator state failed"
);
let mut completed = false;
check_status_or_throw!(
env,
unsafe { sys::napi_get_value_bool(env, generator_state, &mut completed) },
"Get generator state failed"
);
let mut result = std::ptr::null_mut();
check_status_or_throw!(
env,
unsafe { sys::napi_create_object(env, &mut result) },
"Failed to create iterator result object",
);
if !completed {
let g = unsafe { Box::leak(Box::from_raw(generator_ptr as *mut T)) };
let item = if argc == 0 {
g.next(None)
} else {
g.next(match unsafe { T::Next::from_napi_value(env, argv[0]) } {
Ok(input) => Some(input),
Err(e) => {
unsafe {
sys::napi_throw_error(
env,
format!("{}", e.status).as_ptr() as *mut c_char,
e.reason.as_ptr() as *mut c_char,
)
};
None
}
})
};
if let Some(value) = item {
set_generator_value(env, result, value);
} else {
completed = true;
}
}
let mut completed_value = ptr::null_mut();
check_status_or_throw!(
env,
unsafe { sys::napi_get_boolean(env, completed, &mut completed_value) },
"Failed to create completed value"
);
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
result,
"done\0".as_ptr() as *const std::os::raw::c_char,
completed_value,
)
},
"Failed to set iterator result done",
);
result
}
extern "C" fn generator_return<T: Generator>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> sys::napi_value {
let mut this = ptr::null_mut();
let mut argv: [sys::napi_value; 1] = [ptr::null_mut()];
let mut argc = 1;
let mut generator_ptr = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_get_cb_info(
env,
info,
&mut argc,
argv.as_mut_ptr(),
&mut this,
&mut generator_ptr,
)
},
"Get callback info from generator function failed"
);
let g = unsafe { Box::leak(Box::from_raw(generator_ptr as *mut T)) };
if argc == 0 {
g.complete(None);
} else {
g.complete(Some(
match unsafe { T::Return::from_napi_value(env, argv[0]) } {
Ok(input) => input,
Err(e) => {
unsafe {
sys::napi_throw_error(
env,
format!("{}", e.status).as_ptr() as *mut c_char,
e.reason.as_ptr() as *mut c_char,
)
};
return ptr::null_mut();
}
},
));
}
let mut generator_state = ptr::null_mut();
check_status_or_throw!(
env,
unsafe { sys::napi_get_boolean(env, true, &mut generator_state) },
"Create generator state failed"
);
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
this,
GENERATOR_STATE_KEY.as_ptr() as *const c_char,
generator_state,
)
},
"Get generator state failed"
);
let mut result = std::ptr::null_mut();
check_status_or_throw!(
env,
unsafe { sys::napi_create_object(env, &mut result) },
"Failed to create iterator result object",
);
if argc > 0 {
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
result,
"value\0".as_ptr() as *const std::os::raw::c_char,
argv[0],
)
},
"Failed to set iterator result value",
);
}
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
result,
"done\0".as_ptr() as *const std::os::raw::c_char,
generator_state,
)
},
"Failed to set iterator result done",
);
result
}
extern "C" fn generator_throw<T: Generator>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> sys::napi_value {
let mut this = ptr::null_mut();
let mut argv: [sys::napi_value; 1] = [ptr::null_mut()];
let mut argc = 1;
let mut generator_ptr = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_get_cb_info(
env,
info,
&mut argc,
argv.as_mut_ptr(),
&mut this,
&mut generator_ptr,
)
},
"Get callback info from generator function failed"
);
let g = unsafe { Box::leak(Box::from_raw(generator_ptr as *mut T)) };
let catch_result = if argc == 0 {
let mut undefined = ptr::null_mut();
check_status_or_throw!(
env,
unsafe { sys::napi_get_undefined(env, &mut undefined) },
"Get undefined failed"
);
g.catch(
Env(env),
Unknown(Value {
env,
value: undefined,
value_type: crate::ValueType::Undefined,
}),
)
} else {
g.catch(
Env(env),
Unknown(Value {
env,
value: argv[0],
value_type: crate::ValueType::Unknown,
}),
)
};
let mut result = ptr::null_mut();
check_status_or_throw!(
env,
unsafe { sys::napi_create_object(env, &mut result) },
"Failed to create iterator result object",
);
let mut generator_state = ptr::null_mut();
let mut generator_state_value = false;
match catch_result {
Err(e) => {
generator_state_value = true;
check_status_or_throw!(
env,
unsafe { sys::napi_get_boolean(env, generator_state_value, &mut generator_state) },
"Create generator state failed"
);
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
this,
GENERATOR_STATE_KEY.as_ptr() as *const c_char,
generator_state,
)
},
"Get generator state failed"
);
let throw_status = unsafe { sys::napi_throw(env, e.0.value) };
debug_assert!(
throw_status == sys::Status::napi_ok,
"Failed to throw error {}",
crate::Status::from(throw_status)
);
return ptr::null_mut();
}
Ok(Some(v)) => {
set_generator_value(env, result, v);
}
Ok(None) => {
generator_state_value = true;
}
}
check_status_or_throw!(
env,
unsafe { sys::napi_get_boolean(env, generator_state_value, &mut generator_state) },
"Create generator state failed"
);
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
this,
GENERATOR_STATE_KEY.as_ptr() as *const c_char,
generator_state,
)
},
"Get generator state failed"
);
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
result,
"done\0".as_ptr() as *const c_char,
generator_state,
)
},
"Get generator state failed"
);
result
}
fn set_generator_value<V: ToNapiValue>(env: sys::napi_env, result: sys::napi_value, value: V) {
match unsafe { ToNapiValue::to_napi_value(env, value) } {
Ok(val) => {
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
result,
"value\0".as_ptr() as *const std::os::raw::c_char,
val,
)
},
"Failed to set iterator result value",
);
}
Err(e) => {
unsafe {
sys::napi_throw_error(
env,
format!("{}", e.status).as_ptr() as *mut c_char,
e.reason.as_ptr() as *mut c_char,
)
};
}
}
}

View file

@ -26,6 +26,7 @@ mod symbol;
mod task; mod task;
mod value_ref; mod value_ref;
pub use crate::js_values::JsUnknown as Unknown;
#[cfg(feature = "napi5")] #[cfg(feature = "napi5")]
pub use crate::JsDate as Date; pub use crate::JsDate as Date;
pub use array::*; pub use array::*;
@ -159,7 +160,7 @@ pub trait ValidateNapiValue: FromNapiValue + TypeName {
impl<T: TypeName> TypeName for Option<T> { impl<T: TypeName> TypeName for Option<T> {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"Option" T::type_name()
} }
fn value_type() -> ValueType { fn value_type() -> ValueType {

View file

@ -170,6 +170,10 @@ impl<T: 'static, S: 'static> SharedReference<T, S> {
}) })
} }
pub fn clone_owner(&self, env: Env) -> Result<Reference<T>> {
self.owner.clone(env)
}
/// Safety to share because caller can provide `Env` /// Safety to share because caller can provide `Env`
pub fn share_with<U: 'static, F: FnOnce(&'static mut S) -> Result<U>>( pub fn share_with<U: 'static, F: FnOnce(&'static mut S) -> Result<U>>(
self, self,

View file

@ -5,6 +5,7 @@ use std::rc::Rc;
pub use callback_info::*; pub use callback_info::*;
pub use ctor::ctor; pub use ctor::ctor;
pub use env::*; pub use env::*;
pub use iterator::Generator;
pub use js_values::*; pub use js_values::*;
pub use module_register::*; pub use module_register::*;
@ -14,12 +15,14 @@ use crate::Status;
mod callback_info; mod callback_info;
mod env; mod env;
mod error; mod error;
pub mod iterator;
mod js_values; mod js_values;
mod module_register; mod module_register;
/// # Safety /// # Safety
/// ///
/// called when node wrapper objects destroyed /// called when node wrapper objects destroyed
#[doc(hidden)]
pub unsafe extern "C" fn raw_finalize_unchecked<T>( pub unsafe extern "C" fn raw_finalize_unchecked<T>(
env: sys::napi_env, env: sys::napi_env,
finalize_data: *mut c_void, finalize_data: *mut c_void,
@ -63,6 +66,7 @@ pub unsafe extern "C" fn raw_finalize_unchecked<T>(
/// # Safety /// # Safety
/// ///
/// called when node buffer is ready for gc /// called when node buffer is ready for gc
#[doc(hidden)]
pub unsafe extern "C" fn drop_buffer( pub unsafe extern "C" fn drop_buffer(
_env: sys::napi_env, _env: sys::napi_env,
finalize_data: *mut c_void, finalize_data: *mut c_void,

View file

@ -376,11 +376,7 @@ unsafe extern "C" fn napi_register_module_v1(
} }
} }
let (ctor, props): (Vec<_>, Vec<_>) = props.iter().partition(|prop| prop.is_ctor); let (ctor, props): (Vec<_>, Vec<_>) = props.iter().partition(|prop| prop.is_ctor);
// one or more or zero?
// zero is for `#[napi(task)]`
if ctor.is_empty() && props.is_empty() {
continue;
}
let ctor = ctor.get(0).map(|c| c.raw().method.unwrap()).unwrap_or(noop); let ctor = ctor.get(0).map(|c| c.raw().method.unwrap()).unwrap_or(noop);
let raw_props: Vec<_> = props.iter().map(|prop| prop.raw()).collect(); let raw_props: Vec<_> = props.iter().map(|prop| prop.raw()).collect();

View file

@ -108,6 +108,7 @@ pub use napi_sys as sys;
pub use async_work::AsyncWorkPromise; pub use async_work::AsyncWorkPromise;
pub use call_context::CallContext; pub use call_context::CallContext;
pub use bindgen_runtime::iterator;
pub use env::*; pub use env::*;
pub use error::*; pub use error::*;
pub use js_values::*; pub use js_values::*;
@ -154,13 +155,33 @@ macro_rules! assert_type_of {
}; };
} }
#[allow(dead_code)] pub use crate::bindgen_runtime::ctor as module_init;
pub(crate) unsafe fn log_js_value<V: AsRef<[sys::napi_value]>>(
pub mod bindgen_prelude {
#[cfg(feature = "compat-mode")]
pub use crate::bindgen_runtime::register_module_exports;
#[cfg(feature = "tokio_rt")]
pub use crate::tokio_runtime::*;
pub use crate::{
assert_type_of, bindgen_runtime::*, check_status, check_status_or_throw, error, error::*, sys,
type_of, JsError, Property, PropertyAttributes, Result, Status, Task, ValueType,
};
}
#[doc(hidden)]
pub mod __private {
pub use crate::bindgen_runtime::{
get_class_constructor, iterator::create_iterator, register_class,
};
use crate::sys;
pub unsafe fn log_js_value<V: AsRef<[sys::napi_value]>>(
// `info`, `log`, `warning` or `error` // `info`, `log`, `warning` or `error`
method: &str, method: &str,
env: sys::napi_env, env: sys::napi_env,
values: V, values: V,
) { ) {
use std::ffi::CString; use std::ffi::CString;
use std::ptr; use std::ptr;
@ -184,19 +205,7 @@ pub(crate) unsafe fn log_js_value<V: AsRef<[sys::napi_value]>>(
ptr::null_mut(), ptr::null_mut(),
) )
}; };
} }
pub use crate::bindgen_runtime::ctor as module_init;
pub mod bindgen_prelude {
#[cfg(feature = "compat-mode")]
pub use crate::bindgen_runtime::register_module_exports;
#[cfg(feature = "tokio_rt")]
pub use crate::tokio_runtime::*;
pub use crate::{
assert_type_of, bindgen_runtime::*, check_status, check_status_or_throw, error, error::*, sys,
type_of, JsError, Property, PropertyAttributes, Result, Status, Task, ValueType,
};
} }
#[cfg(feature = "tokio_rt")] #[cfg(feature = "tokio_rt")]

View file

@ -0,0 +1,51 @@
import test from 'ava'
import { Fib } from '../index'
test('should be able to stop a generator', (t) => {
const fib = new Fib()
const gen = fib[Symbol.iterator]
t.is(typeof gen, 'function')
const iterator = gen()
t.deepEqual(iterator.next(), {
done: false,
value: 1,
})
iterator.next()
iterator.next()
iterator.next()
iterator.next()
t.deepEqual(iterator.next(), {
done: false,
value: 8,
})
t.deepEqual(iterator.return?.(), {
done: true,
})
t.deepEqual(iterator.next(), {
done: true,
})
})
test('should be able to throw to generator', (t) => {
const fib = new Fib()
const gen = fib[Symbol.iterator]
t.is(typeof gen, 'function')
const iterator = gen()
t.deepEqual(iterator.next(), {
done: false,
value: 1,
})
iterator.next()
iterator.next()
iterator.next()
iterator.next()
t.deepEqual(iterator.next(), {
done: false,
value: 8,
})
t.throws(() => iterator.throw!(new Error()))
t.deepEqual(iterator.next(), {
done: true,
})
})

View file

@ -264,6 +264,10 @@ Generated by [AVA](https://avajs.dev).
export class JsClassForEither {␊ export class JsClassForEither {␊
constructor()␊ constructor()␊
}␊ }␊
export class Fib {␊
[Symbol.iterator](): Iterator<number, void, number>
constructor()␊
}␊
export class JsRepo {␊ export class JsRepo {␊
constructor(dir: string)␊ constructor(dir: string)␊
remote(): JsRemote␊ remote(): JsRemote␊

View file

@ -254,6 +254,10 @@ export class ClassWithFactory {
export class JsClassForEither { export class JsClassForEither {
constructor() constructor()
} }
export class Fib {
[Symbol.iterator](): Iterator<number, void, number>
constructor()
}
export class JsRepo { export class JsRepo {
constructor(dir: string) constructor(dir: string)
remote(): JsRemote remote(): JsRemote

View file

@ -0,0 +1,42 @@
use napi::bindgen_prelude::*;
#[napi(iterator)]
pub struct Fib {
current: u32,
next: u32,
}
#[napi]
impl Generator for Fib {
type Yield = u32;
type Next = i32;
type Return = ();
fn next(&mut self, value: Option<Self::Next>) -> Option<Self::Yield> {
match value {
Some(n) => {
self.current = n as u32;
self.next = n as u32 + 1;
}
None => {
let next = self.next;
let current = self.current;
self.current = next;
self.next = current + next;
}
};
Some(self.current)
}
}
#[napi]
#[allow(clippy::new_without_default)]
impl Fib {
#[napi(constructor)]
pub fn new() -> Self {
Fib {
current: 0,
next: 1,
}
}
}

View file

@ -26,6 +26,7 @@ mod error;
mod external; mod external;
mod fn_strict; mod fn_strict;
mod fn_ts_override; mod fn_ts_override;
mod generator;
mod js_mod; mod js_mod;
mod map; mod map;
mod nullable; mod nullable;

View file

@ -1,12 +1,11 @@
use std::thread::sleep; use std::thread::sleep;
use napi::bindgen_prelude::*; use napi::bindgen_prelude::*;
use napi::Task;
struct DelaySum(u32, u32); struct DelaySum(u32, u32);
#[napi] #[napi]
impl Task for DelaySum { impl napi::Task for DelaySum {
type Output = u32; type Output = u32;
type JsValue = u32; type JsValue = u32;