diff --git a/crates/napi/src/bindgen_runtime/js_values.rs b/crates/napi/src/bindgen_runtime/js_values.rs index 371db93d..0fac92d3 100644 --- a/crates/napi/src/bindgen_runtime/js_values.rs +++ b/crates/napi/src/bindgen_runtime/js_values.rs @@ -25,6 +25,7 @@ mod object; #[cfg(all(feature = "tokio_rt", feature = "napi4"))] mod promise; mod promise_raw; +mod scope; #[cfg(feature = "serde-json")] mod serde; mod set; @@ -52,6 +53,7 @@ pub use object::*; #[cfg(all(feature = "tokio_rt", feature = "napi4"))] pub use promise::*; pub use promise_raw::*; +pub use scope::*; #[cfg(feature = "web_stream")] pub use stream::*; pub use string::*; diff --git a/crates/napi/src/bindgen_runtime/js_values/scope.rs b/crates/napi/src/bindgen_runtime/js_values/scope.rs new file mode 100644 index 00000000..034927fe --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/scope.rs @@ -0,0 +1,201 @@ +use std::ptr; + +use crate::{check_status, sys, Env, JsValue, Result}; + +pub struct HandleScope { + pub(crate) scope: sys::napi_handle_scope, +} + +impl HandleScope { + pub fn create(env: &Env) -> Result { + let mut scope = ptr::null_mut(); + check_status!( + unsafe { sys::napi_open_handle_scope(env.0, &mut scope) }, + "Failed to open handle scope" + )?; + Ok(Self { scope }) + } + + pub fn run(self, arg: A, f: impl FnOnce(A) -> Result) -> Result + where + A: JsValuesTuple, + { + let env = arg.env(); + let ret = f(arg); + check_status!( + unsafe { sys::napi_close_handle_scope(env, self.scope) }, + "Failed to close handle scope" + )?; + ret + } +} + +pub trait JsValuesTuple { + fn env(&self) -> sys::napi_env; +} + +impl<'env, T: JsValue<'env>> JsValuesTuple for T { + fn env(&self) -> sys::napi_env { + self.value().env + } +} + +impl<'env, T1: JsValue<'env>, T2: JsValue<'env>> JsValuesTuple for (T1, T2) { + fn env(&self) -> sys::napi_env { + self.0.value().env + } +} + +impl<'env, T1: JsValue<'env>, T2: JsValue<'env>, T3: JsValue<'env>> JsValuesTuple for (T1, T2, T3) { + fn env(&self) -> sys::napi_env { + self.0.value().env + } +} + +impl<'env, T1: JsValue<'env>, T2: JsValue<'env>, T3: JsValue<'env>, T4: JsValue<'env>> JsValuesTuple + for (T1, T2, T3, T4) +{ + fn env(&self) -> sys::napi_env { + self.0.value().env + } +} + +impl< + 'env, + T1: JsValue<'env>, + T2: JsValue<'env>, + T3: JsValue<'env>, + T4: JsValue<'env>, + T5: JsValue<'env>, + > JsValuesTuple for (T1, T2, T3, T4, T5) +{ + fn env(&self) -> sys::napi_env { + self.0.value().env + } +} + +impl< + 'env, + T1: JsValue<'env>, + T2: JsValue<'env>, + T3: JsValue<'env>, + T4: JsValue<'env>, + T5: JsValue<'env>, + T6: JsValue<'env>, + > JsValuesTuple for (T1, T2, T3, T4, T5, T6) +{ + fn env(&self) -> sys::napi_env { + self.0.value().env + } +} + +impl< + 'env, + T1: JsValue<'env>, + T2: JsValue<'env>, + T3: JsValue<'env>, + T4: JsValue<'env>, + T5: JsValue<'env>, + T6: JsValue<'env>, + T7: JsValue<'env>, + > JsValuesTuple for (T1, T2, T3, T4, T5, T6, T7) +{ + fn env(&self) -> sys::napi_env { + self.0.value().env + } +} + +impl< + 'env, + T1: JsValue<'env>, + T2: JsValue<'env>, + T3: JsValue<'env>, + T4: JsValue<'env>, + T5: JsValue<'env>, + T6: JsValue<'env>, + T7: JsValue<'env>, + T8: JsValue<'env>, + > JsValuesTuple for (T1, T2, T3, T4, T5, T6, T7, T8) +{ + fn env(&self) -> sys::napi_env { + self.0.value().env + } +} + +impl< + 'env, + T1: JsValue<'env>, + T2: JsValue<'env>, + T3: JsValue<'env>, + T4: JsValue<'env>, + T5: JsValue<'env>, + T6: JsValue<'env>, + T7: JsValue<'env>, + T8: JsValue<'env>, + T9: JsValue<'env>, + > JsValuesTuple for (T1, T2, T3, T4, T5, T6, T7, T8, T9) +{ + fn env(&self) -> sys::napi_env { + self.0.value().env + } +} + +impl< + 'env, + T1: JsValue<'env>, + T2: JsValue<'env>, + T3: JsValue<'env>, + T4: JsValue<'env>, + T5: JsValue<'env>, + T6: JsValue<'env>, + T7: JsValue<'env>, + T8: JsValue<'env>, + T9: JsValue<'env>, + T10: JsValue<'env>, + > JsValuesTuple for (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) +{ + fn env(&self) -> sys::napi_env { + self.0.value().env + } +} + +impl< + 'env, + T1: JsValue<'env>, + T2: JsValue<'env>, + T3: JsValue<'env>, + T4: JsValue<'env>, + T5: JsValue<'env>, + T6: JsValue<'env>, + T7: JsValue<'env>, + T8: JsValue<'env>, + T9: JsValue<'env>, + T10: JsValue<'env>, + T11: JsValue<'env>, + > JsValuesTuple for (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) +{ + fn env(&self) -> sys::napi_env { + self.0.value().env + } +} + +impl< + 'env, + T1: JsValue<'env>, + T2: JsValue<'env>, + T3: JsValue<'env>, + T4: JsValue<'env>, + T5: JsValue<'env>, + T6: JsValue<'env>, + T7: JsValue<'env>, + T8: JsValue<'env>, + T9: JsValue<'env>, + T10: JsValue<'env>, + T11: JsValue<'env>, + T12: JsValue<'env>, + > JsValuesTuple for (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) +{ + fn env(&self) -> sys::napi_env { + self.0.value().env + } +} diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.md b/examples/napi/__tests__/__snapshots__/values.spec.ts.md index a03296e4..80be2855 100644 --- a/examples/napi/__tests__/__snapshots__/values.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/values.spec.ts.md @@ -824,6 +824,8 @@ Generated by [AVA](https://avajs.dev). value: number␊ }␊ ␊ + export declare function shorterScope(arr: unknown[]): Array␊ + ␊ export declare function shutdownRuntime(): void␊ ␊ export declare function spawnThreadInThread(tsfn: ((err: Error | null, arg: number) => number)): void␊ diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap index cfd2ad44..e6852926 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 d3fcb088..a00a5d60 100644 --- a/examples/napi/__tests__/values.spec.ts +++ b/examples/napi/__tests__/values.spec.ts @@ -232,6 +232,7 @@ import { extendsJavascriptError, shutdownRuntime, callAsyncWithUnknownReturnValue, + shorterScope, } from '../index.cjs' // import other stuff in `#[napi(module_exports)]` import nativeAddon from '../index.cjs' @@ -1660,3 +1661,8 @@ test('extends javascript error', (t) => { test('module exports', (t) => { t.is(nativeAddon.NAPI_RS_SYMBOL, Symbol.for('NAPI_RS_SYMBOL')) }) + +test('shorter scope', (t) => { + const result = shorterScope(['hello', { foo: 'bar' }, 'world', true]) + t.deepEqual(result, [5, 1, 5, 0]) +}) diff --git a/examples/napi/example.wasi-browser.js b/examples/napi/example.wasi-browser.js index e7f885c6..c42fba5b 100644 --- a/examples/napi/example.wasi-browser.js +++ b/examples/napi/example.wasi-browser.js @@ -292,6 +292,7 @@ export const roundtripStr = __napiModule.exports.roundtripStr export const runScript = __napiModule.exports.runScript export const setNullByteProperty = __napiModule.exports.setNullByteProperty export const setSymbolInObj = __napiModule.exports.setSymbolInObj +export const shorterScope = __napiModule.exports.shorterScope export const shutdownRuntime = __napiModule.exports.shutdownRuntime export const spawnThreadInThread = __napiModule.exports.spawnThreadInThread export const Status = __napiModule.exports.Status diff --git a/examples/napi/example.wasi.cjs b/examples/napi/example.wasi.cjs index e94d7b69..53280515 100644 --- a/examples/napi/example.wasi.cjs +++ b/examples/napi/example.wasi.cjs @@ -316,6 +316,7 @@ module.exports.roundtripStr = __napiModule.exports.roundtripStr module.exports.runScript = __napiModule.exports.runScript module.exports.setNullByteProperty = __napiModule.exports.setNullByteProperty module.exports.setSymbolInObj = __napiModule.exports.setSymbolInObj +module.exports.shorterScope = __napiModule.exports.shorterScope module.exports.shutdownRuntime = __napiModule.exports.shutdownRuntime module.exports.spawnThreadInThread = __napiModule.exports.spawnThreadInThread module.exports.Status = __napiModule.exports.Status diff --git a/examples/napi/index.cjs b/examples/napi/index.cjs index e2a9e2ed..b400ec22 100644 --- a/examples/napi/index.cjs +++ b/examples/napi/index.cjs @@ -606,6 +606,7 @@ module.exports.roundtripStr = nativeBinding.roundtripStr module.exports.runScript = nativeBinding.runScript module.exports.setNullByteProperty = nativeBinding.setNullByteProperty module.exports.setSymbolInObj = nativeBinding.setSymbolInObj +module.exports.shorterScope = nativeBinding.shorterScope module.exports.shutdownRuntime = nativeBinding.shutdownRuntime module.exports.spawnThreadInThread = nativeBinding.spawnThreadInThread module.exports.Status = nativeBinding.Status diff --git a/examples/napi/index.d.cts b/examples/napi/index.d.cts index 027c4529..6163d2fc 100644 --- a/examples/napi/index.d.cts +++ b/examples/napi/index.d.cts @@ -786,6 +786,8 @@ export interface Shared { value: number } +export declare function shorterScope(arr: unknown[]): Array + export declare function shutdownRuntime(): void export declare function spawnThreadInThread(tsfn: ((err: Error | null, arg: number) => number)): void diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs index bca8f0b5..9ce5cad7 100644 --- a/examples/napi/src/lib.rs +++ b/examples/napi/src/lib.rs @@ -80,6 +80,7 @@ mod number; mod object; mod promise; mod reference; +mod scope; mod serde; mod set; mod shared; diff --git a/examples/napi/src/scope.rs b/examples/napi/src/scope.rs new file mode 100644 index 00000000..0fa8fd07 --- /dev/null +++ b/examples/napi/src/scope.rs @@ -0,0 +1,21 @@ +use napi::{bindgen_prelude::*, JsString}; + +#[napi] +pub fn shorter_scope(env: &Env, arr: Array) -> Result> { + let len = arr.len(); + let mut result = Vec::with_capacity(len as usize); + for i in 0..len { + let scope = HandleScope::create(env)?; + let value: Unknown = arr.get_element(i)?; + let len = scope.run(value, |v| match v.get_type()? { + ValueType::String => { + let string = unsafe { v.cast::() }?; + Ok(string.utf8_len()? as u32) + } + ValueType::Object => Ok(1), + _ => Ok(0), + })?; + result.push(len); + } + Ok(result) +}