diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 13eb3414..c6893f58 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -32,6 +32,7 @@ pub struct NapiFn { pub catch_unwind: bool, pub unsafe_: bool, pub register_name: Ident, + pub no_export: bool, } #[derive(Debug, Clone)] diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index b574000c..257eef66 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -27,7 +27,7 @@ pub trait TryToTokens { } fn get_intermediate_ident(name: &str) -> Ident { - let new_name = format!("_napi_internal_register_{name}"); + let new_name = format!("{name}_c_callback"); Ident::new(&new_name, Span::call_site()) } diff --git a/crates/backend/src/codegen/fn.rs b/crates/backend/src/codegen/fn.rs index ad11d615..7ab73df6 100644 --- a/crates/backend/src/codegen/fn.rs +++ b/crates/backend/src/codegen/fn.rs @@ -726,10 +726,14 @@ impl NapiFn { let module_register_name = &self.register_name; let intermediate_ident = get_intermediate_ident(&name_str); let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref()); - let cb_name = Ident::new(&format!("{name_str}_js_function"), Span::call_site()); + let cb_name = Ident::new( + &format!("_napi_rs_internal_register_{name_str}"), + Span::call_site(), + ); if self.module_exports { return quote! { + #[doc(hidden)] #[allow(non_snake_case)] #[allow(clippy::all)] unsafe fn #cb_name(env: napi::bindgen_prelude::sys::napi_env, exports: napi::bindgen_prelude::sys::napi_value) -> napi::bindgen_prelude::Result { @@ -737,6 +741,7 @@ impl NapiFn { Ok(exports) } + #[doc(hidden)] #[allow(clippy::all)] #[allow(non_snake_case)] #[cfg(all(not(test), not(target_family = "wasm")))] @@ -755,7 +760,32 @@ impl NapiFn { }; } + let register_module_export_tokens = if self.no_export { + quote! {} + } else { + quote! { + #[doc(hidden)] + #[allow(clippy::all)] + #[allow(non_snake_case)] + #[cfg(all(not(test), not(target_family = "wasm")))] + #[napi::ctor::ctor(crate_path=::napi::ctor)] + fn #module_register_name() { + napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name); + } + + #[doc(hidden)] + #[allow(clippy::all)] + #[allow(non_snake_case)] + #[cfg(all(not(test), target_family = "wasm"))] + #[no_mangle] + extern "C" fn #module_register_name() { + napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name); + } + } + }; + quote! { + #[doc(hidden)] #[allow(non_snake_case)] #[allow(clippy::all)] unsafe fn #cb_name(env: napi::bindgen_prelude::sys::napi_env) -> napi::bindgen_prelude::Result { @@ -773,25 +803,10 @@ impl NapiFn { "Failed to register function `{}`", #name_str, )?; - napi::bindgen_prelude::register_js_function(#js_name, #cb_name, Some(#intermediate_ident)); Ok(fn_ptr) } - #[allow(clippy::all)] - #[allow(non_snake_case)] - #[cfg(all(not(test), not(target_family = "wasm")))] - #[napi::ctor::ctor(crate_path=::napi::ctor)] - fn #module_register_name() { - napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name); - } - - #[allow(clippy::all)] - #[allow(non_snake_case)] - #[cfg(all(not(test), target_family = "wasm"))] - #[no_mangle] - extern "C" fn #module_register_name() { - napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name); - } + #register_module_export_tokens } } } diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 29df6a34..fab5926c 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -487,7 +487,7 @@ pub fn ty_to_ts_type( Some((fill_ty(known_ty, union_args), false)) } } else { - let filtered_args = + let mut filtered_args = if let Some(arg_indices) = KNOWN_TYPES_IGNORE_ARG.get(rust_ty.as_str()) { args .enumerate() @@ -497,6 +497,9 @@ pub fn ty_to_ts_type( } else { args.collect::>() }; + if rust_ty.starts_with("Function") && filtered_args.is_empty() { + filtered_args = vec!["arg?: unknown".to_owned(), "unknown".to_owned()]; + } Some((fill_ty(known_ty, filtered_args), false)) } diff --git a/crates/backend/src/typegen/fn.rs b/crates/backend/src/typegen/fn.rs index be988212..3cd64ef5 100644 --- a/crates/backend/src/typegen/fn.rs +++ b/crates/backend/src/typegen/fn.rs @@ -67,7 +67,7 @@ impl FromIterator for FnArgList { impl ToTypeDef for NapiFn { fn to_type_def(&self) -> Option { - if self.skip_typescript || self.module_exports { + if self.skip_typescript || self.module_exports || self.no_export { return None; } diff --git a/crates/macro/src/parser/attrs.rs b/crates/macro/src/parser/attrs.rs index 84a07ea9..2243735c 100644 --- a/crates/macro/src/parser/attrs.rs +++ b/crates/macro/src/parser/attrs.rs @@ -80,6 +80,7 @@ macro_rules! attrgen { (discriminant, Discriminant(Span, String, Span)), (transparent, Transparent(Span)), (array, Array(Span)), + (no_export, NoExport(Span)), // impl later // (inspectable, Inspectable(Span)), diff --git a/crates/macro/src/parser/mod.rs b/crates/macro/src/parser/mod.rs index cb601950..46011757 100644 --- a/crates/macro/src/parser/mod.rs +++ b/crates/macro/src/parser/mod.rs @@ -728,6 +728,13 @@ fn napi_fn_from_decl( bail_span!(sig.ident, "module_exports fn can't have generic parameters"); } + if opts.no_export().is_some() { + bail_span!( + sig.ident, + "#[napi(no_export)] can not be used with module_exports attribute" + ); + } + for arg in args.iter() { match &arg.kind { NapiFnArgKind::Callback(_) => { @@ -897,6 +904,7 @@ fn napi_fn_from_decl( catch_unwind: opts.catch_unwind().is_some(), unsafe_: sig.unsafety.is_some(), register_name: get_register_ident(ident.to_string().as_str()), + no_export: opts.no_export().is_some(), }) }) } @@ -964,6 +972,12 @@ impl ParseNapi for syn::ItemStruct { "#[napi(catch_unwind)] can only be applied to a function or method." ); } + if opts.no_export().is_some() { + bail_span!( + self, + "#[napi(no_export)] can only be applied to a function." + ); + } if opts.object().is_some() && opts.custom_finalize().is_some() { bail_span!(self, "Custom finalize is not supported for #[napi(object)]"); } @@ -999,6 +1013,12 @@ impl ParseNapi for syn::ItemImpl { "#[napi(catch_unwind)] can only be applied to a function or method." ); } + if opts.no_export().is_some() { + bail_span!( + self, + "#[napi(no_export)] can only be applied to a function." + ); + } // #[napi] macro will be remove from impl items after converted to ast let napi = self.convert_to_ast(opts); self.to_tokens(tokens); @@ -1031,6 +1051,12 @@ impl ParseNapi for syn::ItemEnum { "#[napi(catch_unwind)] can only be applied to a function or method." ); } + if opts.no_export().is_some() { + bail_span!( + self, + "#[napi(no_export)] can only be applied to a function." + ); + } let napi = self.convert_to_ast(opts); self.to_tokens(tokens); @@ -1061,6 +1087,12 @@ impl ParseNapi for syn::ItemConst { "#[napi(catch_unwind)] can only be applied to a function or method." ); } + if opts.no_export().is_some() { + bail_span!( + self, + "#[napi(no_export)] can only be applied to a function." + ); + } let napi = self.convert_to_ast(opts); self.to_tokens(tokens); napi @@ -1090,6 +1122,12 @@ impl ParseNapi for syn::ItemType { "#[napi(catch_unwind)] can only be applied to a function or method." ); } + if opts.no_export().is_some() { + bail_span!( + self, + "#[napi(no_export)] can only be applied to a function." + ); + } let napi = self.convert_to_ast(opts); self.to_tokens(tokens); napi diff --git a/crates/napi/src/bindgen_runtime/module_register.rs b/crates/napi/src/bindgen_runtime/module_register.rs index f29e0bf1..c946a785 100644 --- a/crates/napi/src/bindgen_runtime/module_register.rs +++ b/crates/napi/src/bindgen_runtime/module_register.rs @@ -94,12 +94,6 @@ impl ModuleClassProperty { } } -type FnRegisterMap = PersistedPerInstanceHashMap< - ExportRegisterCallback, - (sys::napi_callback, &'static str), - FxBuildHasher, ->; - #[cfg(not(feature = "noop"))] static MODULE_REGISTER_CALLBACK: LazyLock = LazyLock::new(Default::default); #[cfg(not(feature = "noop"))] @@ -113,7 +107,6 @@ static MODULE_COUNT: AtomicUsize = AtomicUsize::new(0); static FIRST_MODULE_REGISTERED: AtomicBool = AtomicBool::new(false); thread_local! { static REGISTERED_CLASSES: LazyCell = LazyCell::new(Default::default); - static FN_REGISTER_MAP: LazyCell = LazyCell::new(Default::default); } #[cfg(all(feature = "napi4", not(feature = "noop")))] pub(crate) static CUSTOM_GC_TSFN: std::sync::atomic::AtomicPtr = @@ -194,19 +187,6 @@ pub fn register_module_export_hook(cb: ExportRegisterHookCallback) { #[doc(hidden)] pub fn register_module_export_hook(_cb: ExportRegisterHookCallback) {} -#[doc(hidden)] -pub fn register_js_function( - name: &'static str, - cb: ExportRegisterCallback, - c_fn: sys::napi_callback, -) { - FN_REGISTER_MAP.with(|cell| { - cell.borrow_mut(|inner| { - inner.insert(cb, (c_fn, name)); - }) - }); -} - #[doc(hidden)] pub fn get_class_constructor(js_name: &'static str) -> Option { REGISTERED_CLASSES.with(|cell| cell.borrow_mut(|map| map.get(js_name).copied())) @@ -239,41 +219,6 @@ pub fn register_class( ) { } -/// Get `C Callback` from defined Rust `fn` -/// ```rust -/// #[napi] -/// fn some_fn() -> u32 { -/// 1 -/// } -/// -/// #[napi] -/// fn create_obj(env: Env) -> Result { -/// let mut obj = env.create_object()?; -/// obj.define_property(&[Property::new("getter")?.with_getter(get_c_callback(some_fn_js_function)?)])?; -/// Ok(obj) -/// } -/// ``` -/// -/// ```js -/// console.log(createObj().getter) // 1 -/// ``` -/// -pub fn get_c_callback(raw_fn: ExportRegisterCallback) -> Result { - FN_REGISTER_MAP.with(|cell| { - cell.borrow_mut(|inner| { - inner - .get(&raw_fn) - .and_then(|(cb, _name)| *cb) - .ok_or_else(|| { - crate::Error::new( - crate::Status::InvalidArg, - "JavaScript function does not exist".to_owned(), - ) - }) - }) - }) -} - #[cfg(all(target_family = "wasm", not(feature = "noop")))] #[no_mangle] unsafe extern "C" fn napi_register_wasm_v1( diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.md b/examples/napi/__tests__/__snapshots__/values.spec.ts.md index 8e9edc89..dad9010b 100644 --- a/examples/napi/__tests__/__snapshots__/values.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/values.spec.ts.md @@ -497,6 +497,8 @@ Generated by [AVA](https://avajs.dev). ␊ export declare function createExternalTypedArray(): Uint32Array␊ ␊ + export declare function createFunction(): (arg: number) => number␊ + ␊ export declare function createObj(): object␊ ␊ export declare function createObjectRef(): object␊ @@ -573,7 +575,7 @@ Generated by [AVA](https://avajs.dev). ␊ export declare function either4(input: string | number | boolean | Obj): number␊ ␊ - export declare function eitherBoolOrFunction(input: boolean | (any)): void␊ + export declare function eitherBoolOrFunction(input: boolean | ((arg?: unknown) => unknown)): void␊ ␊ export declare function eitherBoolOrTuple(input: boolean | [boolean, string]): void␊ ␊ @@ -934,7 +936,7 @@ Generated by [AVA](https://avajs.dev). ␊ export declare function testSerdeRoundtrip(data: any): any␊ ␊ - export declare function threadsafeFunctionBuildThrowErrorWithStatus(cb: any): void␊ + export declare function threadsafeFunctionBuildThrowErrorWithStatus(cb: (arg?: unknown) => unknown): void␊ ␊ export declare function threadsafeFunctionClosureCapture(defaultValue: Animal, func: (arg: Animal) => void): void␊ ␊ diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap index 6ee0daf1..e213c691 100644 Binary files a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap and b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap differ diff --git a/examples/napi/__tests__/values.spec.ts b/examples/napi/__tests__/values.spec.ts index f3bdfa90..84a359cd 100644 --- a/examples/napi/__tests__/values.spec.ts +++ b/examples/napi/__tests__/values.spec.ts @@ -196,6 +196,7 @@ import { type AliasedStruct, returnObjectOnlyToJs, buildThreadsafeFunctionFromFunction, + buildThreadsafeFunctionFromFunctionCalleeHandle, createOptionalExternal, getOptionalExternal, mutateOptionalExternal, @@ -255,6 +256,7 @@ import { uint8ArrayFromExternal, Thing, ThingList, + createFunction, } from '../index.cjs' // import other stuff in `#[napi(module_exports)]` import nativeAddon from '../index.cjs' @@ -424,6 +426,8 @@ test('function call', async (t) => { referenceAsCallback((a, b) => a + b, 42, 10), 52, ) + const fn = createFunction() + t.is(fn(42), 242) }) test('class', (t) => { @@ -1667,6 +1671,10 @@ Napi4Test('build ThreadsafeFunction from Function', (t) => { buildThreadsafeFunctionFromFunction(fn) + t.notThrows(() => { + buildThreadsafeFunctionFromFunctionCalleeHandle(() => {}) + }) + return subject.pipe(take(3)) }) diff --git a/examples/napi/example.wasi-browser.js b/examples/napi/example.wasi-browser.js index 13f20b00..b6a9f2a6 100644 --- a/examples/napi/example.wasi-browser.js +++ b/examples/napi/example.wasi-browser.js @@ -185,6 +185,7 @@ export const createExternalBufferSlice = __napiModule.exports.createExternalBuff export const createExternalRef = __napiModule.exports.createExternalRef export const createExternalString = __napiModule.exports.createExternalString export const createExternalTypedArray = __napiModule.exports.createExternalTypedArray +export const createFunction = __napiModule.exports.createFunction export const createObj = __napiModule.exports.createObj export const createObjectRef = __napiModule.exports.createObjectRef export const createObjectWithClassField = __napiModule.exports.createObjectWithClassField diff --git a/examples/napi/example.wasi.cjs b/examples/napi/example.wasi.cjs index 8d76caeb..785c7b24 100644 --- a/examples/napi/example.wasi.cjs +++ b/examples/napi/example.wasi.cjs @@ -209,6 +209,7 @@ module.exports.createExternalBufferSlice = __napiModule.exports.createExternalBu module.exports.createExternalRef = __napiModule.exports.createExternalRef module.exports.createExternalString = __napiModule.exports.createExternalString module.exports.createExternalTypedArray = __napiModule.exports.createExternalTypedArray +module.exports.createFunction = __napiModule.exports.createFunction module.exports.createObj = __napiModule.exports.createObj module.exports.createObjectRef = __napiModule.exports.createObjectRef module.exports.createObjectWithClassField = __napiModule.exports.createObjectWithClassField diff --git a/examples/napi/index.cjs b/examples/napi/index.cjs index daf8348d..85a4090e 100644 --- a/examples/napi/index.cjs +++ b/examples/napi/index.cjs @@ -500,6 +500,7 @@ module.exports.createExternalBufferSlice = nativeBinding.createExternalBufferSli module.exports.createExternalRef = nativeBinding.createExternalRef module.exports.createExternalString = nativeBinding.createExternalString module.exports.createExternalTypedArray = nativeBinding.createExternalTypedArray +module.exports.createFunction = nativeBinding.createFunction module.exports.createObj = nativeBinding.createObj module.exports.createObjectRef = nativeBinding.createObjectRef module.exports.createObjectWithClassField = nativeBinding.createObjectWithClassField diff --git a/examples/napi/index.d.cts b/examples/napi/index.d.cts index 33f53a80..4038594a 100644 --- a/examples/napi/index.d.cts +++ b/examples/napi/index.d.cts @@ -459,6 +459,8 @@ export declare function createExternalString(content: string): ExternalObject number + export declare function createObj(): object export declare function createObjectRef(): object @@ -535,7 +537,7 @@ export declare function either3(input: string | number | boolean): number export declare function either4(input: string | number | boolean | Obj): number -export declare function eitherBoolOrFunction(input: boolean | (any)): void +export declare function eitherBoolOrFunction(input: boolean | ((arg?: unknown) => unknown)): void export declare function eitherBoolOrTuple(input: boolean | [boolean, string]): void @@ -896,7 +898,7 @@ export declare function testSerdeBufferBytes(obj: object): bigint export declare function testSerdeRoundtrip(data: any): any -export declare function threadsafeFunctionBuildThrowErrorWithStatus(cb: any): void +export declare function threadsafeFunctionBuildThrowErrorWithStatus(cb: (arg?: unknown) => unknown): void export declare function threadsafeFunctionClosureCapture(defaultValue: Animal, func: (arg: Animal) => void): void diff --git a/examples/napi/src/function.rs b/examples/napi/src/function.rs index 2d7c89ee..e413d9dd 100644 --- a/examples/napi/src/function.rs +++ b/examples/napi/src/function.rs @@ -1,5 +1,3 @@ -#![allow(deprecated)] - use napi::{ bindgen_prelude::{ClassInstance, FnArgs, Function, FunctionRef, PromiseRaw}, threadsafe_function::ThreadsafeFunctionCallMode, @@ -140,3 +138,13 @@ pub fn build_threadsafe_function_from_function_callee_handle( Ok(()) } + +#[napi] +pub fn create_function(env: &Env) -> Result> { + env.create_function("customFunction", no_export_function_c_callback) +} + +#[napi(no_export)] +pub fn no_export_function(input: u32) -> u32 { + input + 200 +} diff --git a/examples/napi/src/object.rs b/examples/napi/src/object.rs index 96ff3012..9e931abc 100644 --- a/examples/napi/src/object.rs +++ b/examples/napi/src/object.rs @@ -96,7 +96,7 @@ pub fn create_obj_with_property(env: &Env) -> Result> { .with_value(&arraybuffer), Property::new() .with_utf8_name("getter")? - .with_getter(get_c_callback(getter_from_obj_js_function)?), + .with_getter(getter_from_obj_c_callback), ])?; Ok(obj) }