Merge pull request #1270 from napi-rs/custom-finalize

feat(napi): allow implement custom finalize logic for Class
This commit is contained in:
LongYinan 2022-08-17 14:25:51 +08:00 committed by GitHub
commit 8d0045f5b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 136 additions and 15 deletions

View file

@ -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)]

View file

@ -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
}
}

View file

@ -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)),

View file

@ -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(),
}),
})
}

View file

@ -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();

View file

@ -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))
{

View file

@ -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␊

View file

@ -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')

View file

@ -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

View file

@ -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(())
}
}