Skip to content

Commit

Permalink
Merge pull request #1270 from napi-rs/custom-finalize
Browse files Browse the repository at this point in the history
feat(napi): allow implement custom finalize logic for Class
  • Loading branch information
Brooooooklyn committed Aug 17, 2022
2 parents 304bdde + 2385b52 commit 8d0045f
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 15 deletions.
1 change: 1 addition & 0 deletions crates/backend/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
15 changes: 15 additions & 0 deletions crates/backend/src/codegen/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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) {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -453,6 +466,8 @@ impl NapiStruct {
}
}
}

#finalize_trait
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/macro/src/parser/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
47 changes: 42 additions & 5 deletions crates/macro/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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(),
}),
})
}
Expand Down
28 changes: 22 additions & 6 deletions crates/napi/src/bindgen_runtime/callback_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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();
Expand Down
17 changes: 14 additions & 3 deletions crates/napi/src/bindgen_runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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))
{
Expand Down
3 changes: 3 additions & 0 deletions examples/napi/__test__/typegen.spec.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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␊
Expand Down
Binary file modified examples/napi/__test__/typegen.spec.ts.snap
Binary file not shown.
5 changes: 5 additions & 0 deletions examples/napi/__test__/values.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ import {
useTokioWithoutAsync,
getNumArr,
getNestedNumArr,
CustomFinalize,
} from '../'

test('export const', (t) => {
Expand Down Expand Up @@ -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')
Expand Down
3 changes: 3 additions & 0 deletions examples/napi/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 30 additions & 1 deletion examples/napi/src/class.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use napi::{
bindgen_prelude::{Buffer, ClassInstance, This, Uint8Array},
bindgen_prelude::{Buffer, ClassInstance, ObjectFinalize, This, Uint8Array},
Env, Result,
};

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

0 comments on commit 8d0045f

Please sign in to comment.