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 skip_typescript: bool,
pub comments: Vec<String>,
pub parent_is_generator: bool,
}
#[derive(Debug, Clone)]
@ -62,6 +63,7 @@ pub struct NapiStruct {
pub kind: NapiStructKind,
pub js_mod: Option<String>,
pub comments: Vec<String>,
pub implement_iterator: bool,
}
#[derive(Debug, Clone, PartialEq)]
@ -89,6 +91,9 @@ pub struct NapiImpl {
pub js_name: String,
pub items: Vec<NapiFn>,
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 comments: Vec<String>,
}

View file

@ -128,7 +128,7 @@ impl NapiFn {
} else {
if self.parent.is_some() {
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 let syn::PathArguments::AngleBracketed(
syn::AngleBracketedGenericArguments { args: angle_bracketed_args, .. },
@ -281,7 +281,13 @@ impl NapiFn {
let is_return_self = ty_string == "& Self" || ty_string == "&mut Self";
if self.kind == FnKind::Constructor {
if self.is_ret_result {
quote! { cb.construct(#js_name, #ret?) }
if self.parent_is_generator {
quote! { cb.construct_generator(#js_name, #ret?) }
} else {
quote! { cb.construct(#js_name, #ret?) }
}
} else if self.parent_is_generator {
quote! { cb.construct_generator(#js_name, #ret) }
} else {
quote! { cb.construct(#js_name, #ret) }
}

View file

@ -186,13 +186,19 @@ impl NapiStruct {
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! {
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)
.and_then(|cb| unsafe { cb.construct(#js_name_str, #construct) })
.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__>()
@ -218,18 +224,21 @@ impl NapiStruct {
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(name);
quote! {
impl napi::bindgen_prelude::ToNapiValue for #name {
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::bindgen_prelude::get_class_constructor(#js_name_str) {
let wrapped_value = Box::into_raw(Box::new(val)) as *mut std::ffi::c_void;
#name::new_instance(env, wrapped_value, ctor_ref)
if let Some(ctor_ref) = napi::__private::get_class_constructor(#js_name_str) {
let wrapped_value = Box::into_raw(Box::new(val));
let instance_value = #name::new_instance(env, wrapped_value as *mut std::ffi::c_void, 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 `{}`", #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>> {
if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) {
unsafe {
let wrapped_value = Box::into_raw(Box::new(val)) as *mut std::ffi::c_void;
#name::new_instance(env.raw(), wrapped_value, ctor_ref)?;
napi::bindgen_prelude::Reference::<#name>::from_value_ptr(wrapped_value, env.raw())
let wrapped_value = Box::into_raw(Box::new(val));
let instance_value = #name::new_instance(env.raw(), wrapped_value as *mut std::ffi::c_void, ctor_ref)?;
{
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 {
Err(napi::bindgen_prelude::Error::new(
@ -258,7 +271,7 @@ impl NapiStruct {
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 of class `{}`",
"Failed to get constructor reference of class `{}`",
#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 {
let name = &self.name;
let js_name_str = format!("{}\0", &self.js_name);
@ -331,28 +353,29 @@ impl NapiStruct {
quote! {
impl napi::bindgen_prelude::ToNapiValue for #name {
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> {
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 of class `{}`",
"Failed to get constructor reference of class `{}`",
#js_name_str
)?;
let mut result = std::ptr::null_mut();
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 result),
napi::bindgen_prelude::sys::napi_new_instance(env, ctor, args.len(), args.as_ptr(), &mut instance_value),
"Failed to construct class `{}`",
#js_name_str
)?;
Ok(result)
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))
@ -601,7 +624,7 @@ impl NapiStruct {
#[cfg(all(not(test), not(feature = "noop")))]
#[napi::bindgen_prelude::ctor]
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")))]
#[napi::bindgen_prelude::ctor]
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 {
( $( ($v:ident, $ast:ident), )* ) => {
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum NapiItem {
$($v($ast)),*
}

View file

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

View file

@ -1,7 +1,7 @@
use convert_case::{Case, Casing};
use quote::ToTokens;
use std::fmt::{Display, Formatter};
use syn::Pat;
use syn::{Pat, PathArguments, PathSegment};
use super::{ty_to_ts_type, ToTypeDef, TypeDef};
use crate::{js_doc_from_comments, CallbackArg, FnKind, NapiFn};
@ -122,9 +122,21 @@ impl NapiFn {
.filter_map(|arg| match arg {
crate::NapiFnArgKind::PatType(path) => {
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;
}
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();
// remove mutability from PatIdent
if let Pat::Ident(i) = path.pat.as_mut() {

View file

@ -43,30 +43,56 @@ impl ToTypeDef for NapiImpl {
});
}
Some(TypeDef {
kind: "impl".to_owned(),
name: self.js_name.to_owned(),
original_name: None,
def: self
.items
.iter()
.filter_map(|f| {
if f.skip_typescript {
None
} else {
Some(format!(
"{}{}",
js_doc_from_comments(&f.comments),
f.to_type_def()
.map_or(String::default(), |type_def| type_def.def)
))
}
})
.collect::<Vec<_>>()
.join("\\n"),
js_mod: self.js_mod.to_owned(),
js_doc: "".to_string(),
})
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 {
kind: "impl".to_owned(),
name: self.js_name.to_owned(),
original_name: None,
def: self
.items
.iter()
.filter_map(|f| {
if f.skip_typescript {
None
} else {
Some(format!(
"{}{}",
js_doc_from_comments(&f.comments),
f.to_type_def()
.map_or(String::default(), |type_def| type_def.def)
))
}
})
.collect::<Vec<_>>()
.join("\\n"),
js_mod: self.js_mod.to_owned(),
js_doc: "".to_string(),
})
}
}
}

View file

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

View file

@ -1,7 +1,7 @@
#[macro_use]
pub mod attrs;
use std::cell::Cell;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::str::Chars;
@ -17,10 +17,14 @@ use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::ToTokens;
use syn::ext::IdentExt;
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};
thread_local! {
static GENERATOR_STRUCT: RefCell<HashMap<String, bool>> = Default::default();
}
struct AnyIdent(Ident);
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 {
name: ident,
js_name,
@ -581,6 +599,7 @@ fn napi_fn_from_decl(
ts_args_type: opts.ts_args_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(),
parent_is_generator,
}
})
}
@ -786,6 +805,16 @@ impl ConvertToAST for syn::ItemStruct {
}
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 {
item: NapiItem::Struct(NapiStruct {
@ -795,8 +824,9 @@ impl ConvertToAST for syn::ItemStruct {
fields,
is_tuple,
kind: struct_kind,
js_mod: opts.namespace().map(|(m, _)| m.to_owned()),
js_mod: namespace,
comments: extract_doc_comments(&self.attrs),
implement_iterator,
}),
})
}
@ -819,13 +849,30 @@ impl ConvertToAST for syn::ItemImpl {
let mut struct_js_name = struct_name.to_string().to_case(Case::UpperCamel);
let mut items = vec![];
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() {
if let Some(method) = match item {
syn::ImplItem::Method(m) => Some(m),
syn::ImplItem::Type(m) => {
if m.ident == *"JsValue" {
if let Type::Path(_) = &m.ty {
task_output_type = Some(m.ty.clone());
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 {
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
@ -866,13 +913,18 @@ impl ConvertToAST for syn::ItemImpl {
}
}
let namespace = impl_opts.namespace().map(|(m, _)| m.to_owned());
Ok(Napi {
item: NapiItem::Impl(NapiImpl {
name: struct_name,
js_name: struct_js_name,
items,
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),
}),
})

View file

@ -66,10 +66,10 @@ impl<const N: usize> CallbackInfo<N> {
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 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 initial_finalize: Box<dyn FnOnce()> = Box::new(|| {});
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(
self.env,
this,
value_ref,
value_ref as *mut c_void,
Some(raw_finalize_unchecked::<T>),
ptr::null_mut(),
&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));
Ok(this)
Reference::<T>::add_ref(
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> {

View file

@ -5,7 +5,6 @@ macro_rules! check_status_or_throw {
if let Err(e) = $crate::check_status!($code, $($msg)*) {
#[allow(unused_unsafe)]
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 value_ref;
pub use crate::js_values::JsUnknown as Unknown;
#[cfg(feature = "napi5")]
pub use crate::JsDate as Date;
pub use array::*;
@ -159,7 +160,7 @@ pub trait ValidateNapiValue: FromNapiValue + TypeName {
impl<T: TypeName> TypeName for Option<T> {
fn type_name() -> &'static str {
"Option"
T::type_name()
}
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`
pub fn share_with<U: 'static, F: FnOnce(&'static mut S) -> Result<U>>(
self,

View file

@ -5,6 +5,7 @@ use std::rc::Rc;
pub use callback_info::*;
pub use ctor::ctor;
pub use env::*;
pub use iterator::Generator;
pub use js_values::*;
pub use module_register::*;
@ -14,12 +15,14 @@ use crate::Status;
mod callback_info;
mod env;
mod error;
pub mod iterator;
mod js_values;
mod module_register;
/// # Safety
///
/// called when node wrapper objects destroyed
#[doc(hidden)]
pub unsafe extern "C" fn raw_finalize_unchecked<T>(
env: sys::napi_env,
finalize_data: *mut c_void,
@ -63,6 +66,7 @@ pub unsafe extern "C" fn raw_finalize_unchecked<T>(
/// # Safety
///
/// called when node buffer is ready for gc
#[doc(hidden)]
pub unsafe extern "C" fn drop_buffer(
_env: sys::napi_env,
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);
// 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 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 call_context::CallContext;
pub use bindgen_runtime::iterator;
pub use env::*;
pub use error::*;
pub use js_values::*;
@ -154,38 +155,6 @@ macro_rules! assert_type_of {
};
}
#[allow(dead_code)]
pub(crate) unsafe fn log_js_value<V: AsRef<[sys::napi_value]>>(
// `info`, `log`, `warning` or `error`
method: &str,
env: sys::napi_env,
values: V,
) {
use std::ffi::CString;
use std::ptr;
let mut g = ptr::null_mut();
unsafe { sys::napi_get_global(env, &mut g) };
let mut console = ptr::null_mut();
let console_c_string = CString::new("console").unwrap();
let method_c_string = CString::new(method).unwrap();
unsafe { sys::napi_get_named_property(env, g, console_c_string.as_ptr(), &mut console) };
let mut method_js_fn = ptr::null_mut();
unsafe {
sys::napi_get_named_property(env, console, method_c_string.as_ptr(), &mut method_js_fn)
};
unsafe {
sys::napi_call_function(
env,
console,
method_js_fn,
values.as_ref().len(),
values.as_ref().as_ptr(),
ptr::null_mut(),
)
};
}
pub use crate::bindgen_runtime::ctor as module_init;
pub mod bindgen_prelude {
@ -199,5 +168,45 @@ pub mod bindgen_prelude {
};
}
#[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`
method: &str,
env: sys::napi_env,
values: V,
) {
use std::ffi::CString;
use std::ptr;
let mut g = ptr::null_mut();
unsafe { sys::napi_get_global(env, &mut g) };
let mut console = ptr::null_mut();
let console_c_string = CString::new("console").unwrap();
let method_c_string = CString::new(method).unwrap();
unsafe { sys::napi_get_named_property(env, g, console_c_string.as_ptr(), &mut console) };
let mut method_js_fn = ptr::null_mut();
unsafe {
sys::napi_get_named_property(env, console, method_c_string.as_ptr(), &mut method_js_fn)
};
unsafe {
sys::napi_call_function(
env,
console,
method_js_fn,
values.as_ref().len(),
values.as_ref().as_ptr(),
ptr::null_mut(),
)
};
}
}
#[cfg(feature = "tokio_rt")]
pub extern crate tokio;

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 {␊
constructor()␊
}␊
export class Fib {␊
[Symbol.iterator](): Iterator<number, void, number>
constructor()␊
}␊
export class JsRepo {␊
constructor(dir: string)␊
remote(): JsRemote␊

View file

@ -254,6 +254,10 @@ export class ClassWithFactory {
export class JsClassForEither {
constructor()
}
export class Fib {
[Symbol.iterator](): Iterator<number, void, number>
constructor()
}
export class JsRepo {
constructor(dir: string)
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 fn_strict;
mod fn_ts_override;
mod generator;
mod js_mod;
mod map;
mod nullable;

View file

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