Merge pull request #1270 from napi-rs/custom-finalize
feat(napi): allow implement custom finalize logic for Class
This commit is contained in:
commit
8d0045f5b6
11 changed files with 136 additions and 15 deletions
|
@ -81,6 +81,7 @@ pub struct NapiStruct {
|
|||
pub js_mod: Option<String>,
|
||||
pub comments: Vec<String>,
|
||||
pub implement_iterator: bool,
|
||||
pub use_custom_finalize: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
|
|
@ -278,6 +278,11 @@ impl NapiStruct {
|
|||
let js_name_raw = &self.js_name;
|
||||
let js_name_str = format!("{}\0", js_name_raw);
|
||||
let iterator_implementation = self.gen_iterator_property(name);
|
||||
let finalize_trait = if self.use_custom_finalize {
|
||||
quote! {}
|
||||
} else {
|
||||
quote! { impl napi::bindgen_prelude::ObjectFinalize for #name {} }
|
||||
};
|
||||
quote! {
|
||||
impl napi::bindgen_prelude::ToNapiValue for #name {
|
||||
unsafe fn to_napi_value(
|
||||
|
@ -297,6 +302,8 @@ impl NapiStruct {
|
|||
}
|
||||
}
|
||||
|
||||
#finalize_trait
|
||||
|
||||
impl #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) {
|
||||
|
@ -420,6 +427,12 @@ impl NapiStruct {
|
|||
}
|
||||
};
|
||||
|
||||
let finalize_trait = if self.use_custom_finalize {
|
||||
quote! {}
|
||||
} else {
|
||||
quote! { impl napi::bindgen_prelude::ObjectFinalize for #name {} }
|
||||
};
|
||||
|
||||
quote! {
|
||||
impl napi::bindgen_prelude::ToNapiValue for #name {
|
||||
unsafe fn to_napi_value(
|
||||
|
@ -453,6 +466,8 @@ impl NapiStruct {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#finalize_trait
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ macro_rules! attrgen {
|
|||
(strict, Strict(Span)),
|
||||
(return_if_invalid, ReturnIfInvalid(Span)),
|
||||
(object, Object(Span)),
|
||||
(custom_finalize, CustomFinalize(Span)),
|
||||
(namespace, Namespace(Span, String, Span)),
|
||||
(iterator, Iterator(Span)),
|
||||
(ts_args_type, TsArgsType(Span, String, Span)),
|
||||
|
|
|
@ -741,22 +741,39 @@ impl ParseNapi for syn::ItemStruct {
|
|||
"#[napi] can't be applied to a struct with #[napi(ts_args_type)], #[napi(ts_return_type)], #[napi(skip_typescript)] or #[napi(ts_type)]"
|
||||
);
|
||||
}
|
||||
if opts.return_if_invalid().is_some() {
|
||||
bail_span!(
|
||||
self,
|
||||
"#[napi(return_if_invalid)] can only be applied to a function or method."
|
||||
);
|
||||
}
|
||||
if opts.object().is_some() && opts.custom_finalize().is_some() {
|
||||
bail_span!(self, "Custom finalize is not supported for #[napi(object)]");
|
||||
}
|
||||
let napi = self.convert_to_ast(opts);
|
||||
self.to_tokens(tokens);
|
||||
|
||||
napi
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseNapi for syn::ItemImpl {
|
||||
fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult<Napi> {
|
||||
if opts.ts_args_type().is_some()
|
||||
|| opts.ts_return_type().is_some()
|
||||
|| opts.skip_typescript().is_some()
|
||||
|| opts.ts_type().is_some()
|
||||
|| opts.custom_finalize().is_some()
|
||||
{
|
||||
bail_span!(
|
||||
self,
|
||||
"#[napi] can't be applied to impl with #[napi(ts_args_type)], #[napi(ts_return_type)], #[napi(skip_typescript)] or #[napi(ts_type)]"
|
||||
"#[napi] can't be applied to impl with #[napi(ts_args_type)], #[napi(ts_return_type)], #[napi(skip_typescript)] or #[napi(ts_type)] or #[napi(custom_finalize)]"
|
||||
);
|
||||
}
|
||||
if opts.return_if_invalid().is_some() {
|
||||
bail_span!(
|
||||
self,
|
||||
"#[napi(return_if_invalid)] can only be applied to a function or method."
|
||||
);
|
||||
}
|
||||
// #[napi] macro will be remove from impl items after converted to ast
|
||||
|
@ -766,13 +783,23 @@ impl ParseNapi for syn::ItemImpl {
|
|||
napi
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseNapi for syn::ItemEnum {
|
||||
fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult<Napi> {
|
||||
if opts.ts_args_type().is_some() || opts.ts_return_type().is_some() || opts.ts_type().is_some()
|
||||
if opts.ts_args_type().is_some()
|
||||
|| opts.ts_return_type().is_some()
|
||||
|| opts.ts_type().is_some()
|
||||
|| opts.custom_finalize().is_some()
|
||||
{
|
||||
bail_span!(
|
||||
self,
|
||||
"#[napi] can't be applied to a enum with #[napi(ts_args_type)], #[napi(ts_return_type)] or #[napi(ts_type)]"
|
||||
"#[napi] can't be applied to a enum with #[napi(ts_args_type)], #[napi(ts_return_type)] or #[napi(ts_type)] or #[napi(custom_finalize)]"
|
||||
);
|
||||
}
|
||||
if opts.return_if_invalid().is_some() {
|
||||
bail_span!(
|
||||
self,
|
||||
"#[napi(return_if_invalid)] can only be applied to a function or method."
|
||||
);
|
||||
}
|
||||
let napi = self.convert_to_ast(opts);
|
||||
|
@ -783,11 +810,20 @@ impl ParseNapi for syn::ItemEnum {
|
|||
}
|
||||
impl ParseNapi for syn::ItemConst {
|
||||
fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult<Napi> {
|
||||
if opts.ts_args_type().is_some() || opts.ts_return_type().is_some() || opts.ts_type().is_some()
|
||||
if opts.ts_args_type().is_some()
|
||||
|| opts.ts_return_type().is_some()
|
||||
|| opts.ts_type().is_some()
|
||||
|| opts.custom_finalize().is_some()
|
||||
{
|
||||
bail_span!(
|
||||
self,
|
||||
"#[napi] can't be applied to a const with #[napi(ts_args_type)], #[napi(ts_return_type)] or #[napi(ts_type)]"
|
||||
"#[napi] can't be applied to a const with #[napi(ts_args_type)], #[napi(ts_return_type)] or #[napi(ts_type)] or #[napi(custom_finalize)]"
|
||||
);
|
||||
}
|
||||
if opts.return_if_invalid().is_some() {
|
||||
bail_span!(
|
||||
self,
|
||||
"#[napi(return_if_invalid)] can only be applied to a function or method."
|
||||
);
|
||||
}
|
||||
let napi = self.convert_to_ast(opts);
|
||||
|
@ -930,6 +966,7 @@ impl ConvertToAST for syn::ItemStruct {
|
|||
js_mod: namespace,
|
||||
comments: extract_doc_comments(&self.attrs),
|
||||
implement_iterator,
|
||||
use_custom_finalize: opts.custom_finalize().is_some(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -67,7 +67,11 @@ impl<const N: usize> CallbackInfo<N> {
|
|||
self.this
|
||||
}
|
||||
|
||||
fn _construct<T: 'static>(&self, js_name: &str, obj: T) -> Result<(sys::napi_value, *mut T)> {
|
||||
fn _construct<T: ObjectFinalize + '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);
|
||||
|
@ -96,11 +100,15 @@ impl<const N: usize> CallbackInfo<N> {
|
|||
Ok((this, value_ref))
|
||||
}
|
||||
|
||||
pub fn construct<T: 'static>(&self, js_name: &str, obj: T) -> Result<sys::napi_value> {
|
||||
pub fn construct<T: ObjectFinalize + '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>(
|
||||
pub fn construct_generator<T: Generator + ObjectFinalize + 'static>(
|
||||
&self,
|
||||
js_name: &str,
|
||||
obj: T,
|
||||
|
@ -110,11 +118,15 @@ impl<const N: usize> CallbackInfo<N> {
|
|||
Ok(instance)
|
||||
}
|
||||
|
||||
pub fn factory<T: 'static>(&self, js_name: &str, obj: T) -> Result<sys::napi_value> {
|
||||
pub fn factory<T: ObjectFinalize + 'static>(
|
||||
&self,
|
||||
js_name: &str,
|
||||
obj: T,
|
||||
) -> Result<sys::napi_value> {
|
||||
self._factory(js_name, obj).map(|(value, _)| value)
|
||||
}
|
||||
|
||||
pub fn generator_factory<T: Generator + 'static>(
|
||||
pub fn generator_factory<T: ObjectFinalize + Generator + 'static>(
|
||||
&self,
|
||||
js_name: &str,
|
||||
obj: T,
|
||||
|
@ -124,7 +136,11 @@ impl<const N: usize> CallbackInfo<N> {
|
|||
Ok(instance)
|
||||
}
|
||||
|
||||
fn _factory<T: 'static>(&self, js_name: &str, obj: T) -> Result<(sys::napi_value, *mut T)> {
|
||||
fn _factory<T: ObjectFinalize + 'static>(
|
||||
&self,
|
||||
js_name: &str,
|
||||
obj: T,
|
||||
) -> Result<(sys::napi_value, *mut T)> {
|
||||
let this = self.this();
|
||||
let mut instance = ptr::null_mut();
|
||||
let inner = ___CALL_FROM_FACTORY.get_or_default();
|
||||
|
|
|
@ -11,7 +11,7 @@ pub use js_values::*;
|
|||
pub use module_register::*;
|
||||
|
||||
use super::sys;
|
||||
use crate::Status;
|
||||
use crate::{JsError, Result, Status};
|
||||
|
||||
mod callback_info;
|
||||
mod env;
|
||||
|
@ -20,16 +20,27 @@ pub mod iterator;
|
|||
mod js_values;
|
||||
mod module_register;
|
||||
|
||||
pub trait ObjectFinalize: Sized {
|
||||
#[allow(unused)]
|
||||
fn finalize(self, env: Env) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// 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: ObjectFinalize>(
|
||||
env: sys::napi_env,
|
||||
finalize_data: *mut c_void,
|
||||
_finalize_hint: *mut c_void,
|
||||
) {
|
||||
unsafe { Box::from_raw(finalize_data as *mut T) };
|
||||
let data = *unsafe { Box::from_raw(finalize_data as *mut T) };
|
||||
if let Err(err) = data.finalize(unsafe { Env::from_raw(env) }) {
|
||||
let e: JsError = err.into();
|
||||
unsafe { e.throw_into(env) };
|
||||
}
|
||||
if let Some((_, ref_val, finalize_callbacks_ptr)) =
|
||||
REFERENCE_MAP.borrow_mut(|reference_map| reference_map.remove(&finalize_data))
|
||||
{
|
||||
|
|
|
@ -294,6 +294,9 @@ Generated by [AVA](https://avajs.dev).
|
|||
constructor(name: string)␊
|
||||
setName(name: string): void␊
|
||||
}␊
|
||||
export class CustomFinalize {␊
|
||||
constructor(width: number, height: number)␊
|
||||
}␊
|
||||
export class ClassWithFactory {␊
|
||||
name: string␊
|
||||
static withName(name: string): ClassWithFactory␊
|
||||
|
|
Binary file not shown.
|
@ -107,6 +107,7 @@ import {
|
|||
useTokioWithoutAsync,
|
||||
getNumArr,
|
||||
getNestedNumArr,
|
||||
CustomFinalize,
|
||||
} from '../'
|
||||
|
||||
test('export const', (t) => {
|
||||
|
@ -241,6 +242,10 @@ test('class in object field', (t) => {
|
|||
t.is(receiveObjectWithClassField(obj), obj.bird)
|
||||
})
|
||||
|
||||
test('custom finalize class', (t) => {
|
||||
t.notThrows(() => new CustomFinalize(200, 200))
|
||||
})
|
||||
|
||||
test('should be able to create object reference and shared reference', (t) => {
|
||||
const repo = new JsRepo('.')
|
||||
t.is(repo.remote().name(), 'origin')
|
||||
|
|
3
examples/napi/index.d.ts
vendored
3
examples/napi/index.d.ts
vendored
|
@ -284,6 +284,9 @@ export class NotWritableClass {
|
|||
constructor(name: string)
|
||||
setName(name: string): void
|
||||
}
|
||||
export class CustomFinalize {
|
||||
constructor(width: number, height: number)
|
||||
}
|
||||
export class ClassWithFactory {
|
||||
name: string
|
||||
static withName(name: string): ClassWithFactory
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use napi::{
|
||||
bindgen_prelude::{Buffer, ClassInstance, This, Uint8Array},
|
||||
bindgen_prelude::{Buffer, ClassInstance, ObjectFinalize, This, Uint8Array},
|
||||
Env, Result,
|
||||
};
|
||||
|
||||
|
@ -361,3 +361,32 @@ impl NotWritableClass {
|
|||
self.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(custom_finalize)]
|
||||
pub struct CustomFinalize {
|
||||
width: u32,
|
||||
height: u32,
|
||||
inner: Vec<u8>,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl CustomFinalize {
|
||||
#[napi(constructor)]
|
||||
pub fn new(mut env: Env, width: u32, height: u32) -> Result<Self> {
|
||||
let inner = vec![0; (width * height * 4) as usize];
|
||||
let inner_size = inner.len();
|
||||
env.adjust_external_memory(inner_size as i64)?;
|
||||
Ok(Self {
|
||||
width,
|
||||
height,
|
||||
inner,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectFinalize for CustomFinalize {
|
||||
fn finalize(self, mut env: Env) -> Result<()> {
|
||||
env.adjust_external_memory(-(self.inner.len() as i64))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue