feat(napi): support static class factory
This commit is contained in:
parent
e74fe2fb94
commit
e78cdd3c22
15 changed files with 150 additions and 15 deletions
|
@ -34,6 +34,7 @@ pub enum NapiFnArgKind {
|
||||||
pub enum FnKind {
|
pub enum FnKind {
|
||||||
Normal,
|
Normal,
|
||||||
Constructor,
|
Constructor,
|
||||||
|
Factory,
|
||||||
Getter,
|
Getter,
|
||||||
Setter,
|
Setter,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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! {
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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)),
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
|
@ -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<()> {
|
||||||
|
|
|
@ -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␊
|
||||||
|
}␊
|
||||||
`
|
`
|
||||||
|
|
Binary file not shown.
|
@ -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())
|
||||||
|
|
5
examples/napi/index.d.ts
vendored
5
examples/napi/index.d.ts
vendored
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
14
examples/napi/src/class_factory.rs
Normal file
14
examples/napi/src/class_factory.rs
Normal 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 }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue