feat(napi): support static class factory

This commit is contained in:
LongYinan 2021-11-05 18:31:36 +08:00
parent e74fe2fb94
commit e78cdd3c22
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
15 changed files with 150 additions and 15 deletions

View file

@ -34,6 +34,7 @@ pub enum NapiFnArgKind {
pub enum FnKind { pub enum FnKind {
Normal, Normal,
Constructor, Constructor,
Factory,
Getter, Getter,
Setter, Setter,
} }

View file

@ -39,9 +39,25 @@ impl TryToTokens for NapiFn {
} }
}; };
let function_call = let function_call = if args_len == 0
if args_len == 0 && self.fn_self.is_none() && self.kind != FnKind::Constructor { && self.fn_self.is_none()
&& self.kind != FnKind::Constructor
&& self.kind != FnKind::Factory
{
quote! { #native_call } quote! { #native_call }
} else if self.kind == FnKind::Constructor {
quote! {
let call_from_factory = ___CALL_FROM_FACTORY.load(std::sync::atomic::Ordering::Relaxed);
// constructor function is called from class `factory`
// so we should skip the original `constructor` logic
if call_from_factory {
return std::ptr::null_mut();
}
CallbackInfo::<#args_len>::new(env, cb, None).and_then(|mut cb| {
#(#arg_conversions)*
#native_call
})
}
} else { } else {
quote! { quote! {
CallbackInfo::<#args_len>::new(env, cb, None).and_then(|mut cb| { CallbackInfo::<#args_len>::new(env, cb, None).and_then(|mut cb| {
@ -229,6 +245,8 @@ impl NapiFn {
if let Some(ty) = &self.ret { if let Some(ty) = &self.ret {
if self.kind == FnKind::Constructor { if self.kind == FnKind::Constructor {
quote! { cb.construct(#js_name, #ret) } quote! { cb.construct(#js_name, #ret) }
} else if self.kind == FnKind::Factory {
quote! { cb.factory(#js_name, #ret) }
} else if self.is_ret_result { } else if self.is_ret_result {
if self.is_async { if self.is_async {
quote! { quote! {

View file

@ -74,6 +74,7 @@ impl NapiFn {
Some(_) => "", Some(_) => "",
None => "static", None => "static",
}, },
crate::FnKind::Factory => "static",
crate::FnKind::Constructor => "", crate::FnKind::Constructor => "",
crate::FnKind::Getter => "get", crate::FnKind::Getter => "get",
crate::FnKind::Setter => "set", crate::FnKind::Setter => "set",

View file

@ -44,6 +44,7 @@ macro_rules! attrgen {
$mac! { $mac! {
(js_name, JsName(Span, String, Span)), (js_name, JsName(Span, String, Span)),
(constructor, Constructor(Span)), (constructor, Constructor(Span)),
(factory, Factory(Span)),
(getter, Getter(Span, Option<Ident>)), (getter, Getter(Span, Option<Ident>)),
(setter, Setter(Span, Option<Ident>)), (setter, Setter(Span, Option<Ident>)),
(readonly, Readonly(Span)), (readonly, Readonly(Span)),

View file

@ -641,6 +641,10 @@ fn fn_kind(opts: &BindgenAttrs) -> FnKind {
kind = FnKind::Constructor; kind = FnKind::Constructor;
} }
if opts.factory().is_some() {
kind = FnKind::Factory;
}
kind kind
} }

View file

@ -1,10 +1,19 @@
use std::ffi::c_void;
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::{bindgen_prelude::*, check_status, sys, Result}; use crate::{bindgen_prelude::*, check_status, sys, Result};
use std::{ffi::c_void, ptr};
#[doc(hidden)]
/// Determined is `constructor` called from Class `factory`
/// Ugly but works
/// We can even be more ugly without `atomic`
pub static ___CALL_FROM_FACTORY: AtomicBool = AtomicBool::new(false);
pub struct CallbackInfo<const N: usize> { pub struct CallbackInfo<const N: usize> {
env: sys::napi_env, env: sys::napi_env,
this: sys::napi_value, this: sys::napi_value,
args: [sys::napi_value; N], pub args: [sys::napi_value; N],
} }
impl<const N: usize> CallbackInfo<N> { impl<const N: usize> CallbackInfo<N> {
@ -76,6 +85,39 @@ impl<const N: usize> CallbackInfo<N> {
Ok(this) Ok(this)
} }
pub fn factory<T>(&self, js_name: &str, obj: T) -> Result<sys::napi_value> {
let obj = Box::new(obj);
let this = self.this();
let mut instance = ptr::null_mut();
unsafe {
___CALL_FROM_FACTORY.store(true, Ordering::Relaxed);
let status = sys::napi_new_instance(self.env, this, 0, ptr::null_mut(), &mut instance);
___CALL_FROM_FACTORY.store(false, Ordering::Relaxed);
// Error thrown in `constructor`
if status == sys::Status::napi_pending_exception {
let mut exception = ptr::null_mut();
sys::napi_get_and_clear_last_exception(self.env, &mut exception);
sys::napi_throw(self.env, exception);
return Ok(ptr::null_mut());
}
check_status!(
sys::napi_wrap(
self.env,
instance,
Box::into_raw(obj) as *mut std::ffi::c_void,
Some(raw_finalize_unchecked::<T>),
ptr::null_mut(),
&mut std::ptr::null_mut()
),
"Failed to initialize class `{}`",
js_name,
)?;
};
Ok(instance)
}
pub fn unwrap_borrow_mut<T>(&mut self) -> Result<&'static mut T> pub fn unwrap_borrow_mut<T>(&mut self) -> Result<&'static mut T>
where where
T: FromNapiMutRef + TypeName, T: FromNapiMutRef + TypeName,

View file

@ -79,10 +79,10 @@ unsafe extern "C" fn napi_register_module_v1(
let (ctor, props): (Vec<_>, Vec<_>) = props.into_iter().partition(|prop| prop.is_ctor); let (ctor, props): (Vec<_>, Vec<_>) = props.into_iter().partition(|prop| prop.is_ctor);
// one or more or zero? // one or more or zero?
// zero is for `#[napi(task)]` // zero is for `#[napi(task)]`
if ctor.is_empty() { if ctor.is_empty() && props.is_empty() {
continue; continue;
} }
let ctor = ctor[0].raw().method.unwrap(); 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();
let js_class_name = CString::new(js_name).unwrap(); let js_class_name = CString::new(js_name).unwrap();
@ -143,3 +143,10 @@ unsafe extern "C" fn napi_register_module_v1(
exports exports
} }
pub(crate) unsafe extern "C" fn noop(
_env: sys::napi_env,
_info: sys::napi_callback_info,
) -> sys::napi_value {
ptr::null_mut()
}

View file

@ -161,9 +161,22 @@ macro_rules! impl_object_methods {
/// ///
/// This function is safety if env is not null ptr. /// This function is safety if env is not null ptr.
pub unsafe fn throw_into(self, env: sys::napi_env) { pub unsafe fn throw_into(self, env: sys::napi_env) {
#[cfg(debug_assertions)]
let reason = self.0.reason.clone();
#[cfg(debug_assertions)]
let status = self.0.status;
let js_error = self.into_value(env); let js_error = self.into_value(env);
#[cfg(debug_assertions)]
let throw_status = sys::napi_throw(env, js_error); let throw_status = sys::napi_throw(env, js_error);
debug_assert!(throw_status == sys::Status::napi_ok); sys::napi_throw(env, js_error);
#[cfg(debug_assertions)]
assert!(
throw_status == sys::Status::napi_ok,
"Throw error failed, status: [{}], raw message: \"{}\", raw status: [{}]",
Status::from(throw_status),
reason,
Status::from(status)
);
} }
pub fn throw(&self, env: sys::napi_env) -> Result<()> { pub fn throw(&self, env: sys::napi_env) -> Result<()> {

View file

@ -48,9 +48,14 @@ Generated by [AVA](https://avajs.dev).
export class Animal {␊ export class Animal {␊
readonly kind: Kind␊ readonly kind: Kind␊
constructor(kind: Kind, name: string)␊ constructor(kind: Kind, name: string)␊
static withKind(kind: Kind): Animal␊
get name(): string␊ get name(): string␊
set name(name: string)␊ set name(name: string)␊
whoami(): string␊ whoami(): string␊
static getDogKind(): Kind␊ static getDogKind(): Kind␊
}␊ }␊
export class ClassWithFactory {␊
name: string␊
static withName(name: string): ClassWithFactory␊
}␊
` `

View file

@ -15,6 +15,7 @@ import {
getCwd, getCwd,
Animal, Animal,
Kind, Kind,
ClassWithFactory,
CustomNumEnum, CustomNumEnum,
enumToI32, enumToI32,
listObjKeys, listObjKeys,
@ -81,6 +82,20 @@ test('class', (t) => {
t.is(dog.name, '可乐') t.is(dog.name, '可乐')
}) })
test('class factory', (t) => {
const duck = ClassWithFactory.withName('Default')
t.is(duck.name, 'Default')
duck.name = '周黑鸭'
t.is(duck.name, '周黑鸭')
const doge = Animal.withKind(Kind.Dog)
t.is(doge.name, 'Default')
doge.name = '旺财'
t.is(doge.name, '旺财')
})
test('callback', (t) => { test('callback', (t) => {
getCwd((cwd) => { getCwd((cwd) => {
t.is(cwd, process.cwd()) t.is(cwd, process.cwd())

View file

@ -38,8 +38,13 @@ export function getBuffer(): Buffer
export class Animal { export class Animal {
readonly kind: Kind readonly kind: Kind
constructor(kind: Kind, name: string) constructor(kind: Kind, name: string)
static withKind(kind: Kind): Animal
get name(): string get name(): string
set name(name: string) set name(name: string)
whoami(): string whoami(): string
static getDogKind(): Kind static getDogKind(): Kind
} }
export class ClassWithFactory {
name: string
static withName(name: string): ClassWithFactory
}

View file

@ -19,6 +19,14 @@ impl Animal {
Animal { kind, name } Animal { kind, name }
} }
#[napi(factory)]
pub fn with_kind(kind: Kind) -> Self {
Animal {
kind,
name: "Default".to_owned(),
}
}
#[napi(getter)] #[napi(getter)]
pub fn get_name(&self) -> &str { pub fn get_name(&self) -> &str {
self.name.as_str() self.name.as_str()

View file

@ -0,0 +1,14 @@
use napi::bindgen_prelude::*;
#[napi]
pub struct ClassWithFactory {
pub name: String,
}
#[napi]
impl ClassWithFactory {
#[napi(factory)]
pub fn with_name(name: String) -> Self {
Self { name }
}
}

View file

@ -7,6 +7,7 @@ mod array;
mod r#async; mod r#async;
mod callback; mod callback;
mod class; mod class;
mod class_factory;
mod either; mod either;
mod r#enum; mod r#enum;
mod error; mod error;