From e5ee68579a471453bb31f40bb8914371ae6bba7b Mon Sep 17 00:00:00 2001 From: David Herman Date: Fri, 17 Jun 2022 10:16:19 -0700 Subject: [PATCH 01/31] WIP --- crates/neon/src/prelude.rs | 6 +- crates/neon/src/sys/bindings/functions.rs | 9 + crates/neon/src/sys/bindings/mod.rs | 2 +- crates/neon/src/sys/typedarray.rs | 20 ++ crates/neon/src/types/buffer/mod.rs | 2 + crates/neon/src/types/buffer/types.rs | 408 +++++++++++++++++++++- crates/neon/src/types/mod.rs | 9 +- doc/types.jpg | Bin 29464 -> 55021 bytes doc/types.pptx | Bin 44234 -> 45370 bytes test/napi/lib/objects.js | 58 +++ test/napi/src/js/objects.rs | 35 ++ test/napi/src/lib.rs | 6 + 12 files changed, 534 insertions(+), 21 deletions(-) diff --git a/crates/neon/src/prelude.rs b/crates/neon/src/prelude.rs index 13ad48f43..e92fdcc2e 100644 --- a/crates/neon/src/prelude.rs +++ b/crates/neon/src/prelude.rs @@ -11,8 +11,10 @@ pub use crate::{ result::{JsResult, NeonResult, ResultExt as NeonResultExt}, types::{ boxed::{Finalize, JsBox}, - JsArray, JsArrayBuffer, JsBoolean, JsBuffer, JsError, JsFunction, JsNull, JsNumber, - JsObject, JsPromise, JsString, JsTypedArray, JsUndefined, JsValue, Value, + JsArray, JsArrayBuffer, JsBigInt64Array, JsBigUint64Array, JsBoolean, JsBuffer, + JsError, JsFloat32Array, JsFloat64Array, JsFunction, JsInt8Array, JsInt16Array, + JsInt32Array, JsNull, JsNumber, JsObject, JsPromise, JsString, JsTypedArray, + JsUint8Array, JsUint16Array, JsUint32Array, JsUndefined, JsValue, Value, }, }; diff --git a/crates/neon/src/sys/bindings/functions.rs b/crates/neon/src/sys/bindings/functions.rs index fcf05834b..90399b61a 100644 --- a/crates/neon/src/sys/bindings/functions.rs +++ b/crates/neon/src/sys/bindings/functions.rs @@ -90,6 +90,15 @@ mod napi1 { byte_length: *mut usize, ) -> Status; + fn create_typedarray( + env: Env, + type_: TypedArrayType, + length: usize, + arraybuffer: Value, + byte_offset: usize, + result: *mut Value, + ) -> Status; + fn get_typedarray_info( env: Env, typedarray: Value, diff --git a/crates/neon/src/sys/bindings/mod.rs b/crates/neon/src/sys/bindings/mod.rs index 2e26252e6..1f7bb3436 100644 --- a/crates/neon/src/sys/bindings/mod.rs +++ b/crates/neon/src/sys/bindings/mod.rs @@ -3,7 +3,7 @@ //! These types are manually copied from bindings generated from `bindgen`. To //! update, use the following approach: //! -//! * Run `cargo build` with `--cfg neon=dev` at least once to install `nodejs-sys` +//! * Run a debug build of Neon at least once to install `nodejs-sys` //! * Open the generated bindings at `target/debug/build/nodejs-sys-*/out/bindings.rs` //! * Copy the types needed into `types.rs` and `functions.rs` //! * Modify to match Rust naming conventions: diff --git a/crates/neon/src/sys/typedarray.rs b/crates/neon/src/sys/typedarray.rs index d27109dd9..30690ef1a 100644 --- a/crates/neon/src/sys/typedarray.rs +++ b/crates/neon/src/sys/typedarray.rs @@ -39,3 +39,23 @@ pub unsafe fn info(env: Env, value: Local) -> TypedArrayInfo { info.assume_init() } + +pub unsafe fn new( + env: Env, + typ: TypedArrayType, + buffer: Local, + offset: usize, + len: usize, +) -> Result +{ + let mut array = MaybeUninit::uninit(); + let status = napi::create_typedarray(env, typ, len, buffer, offset, array.as_mut_ptr()); + + if status == napi::Status::PendingException { + return Err(status); + } + + assert_eq!(status, napi::Status::Ok); + + Ok(array.assume_init()) +} diff --git a/crates/neon/src/types/buffer/mod.rs b/crates/neon/src/types/buffer/mod.rs index e595f54d8..9ab00e387 100644 --- a/crates/neon/src/types/buffer/mod.rs +++ b/crates/neon/src/types/buffer/mod.rs @@ -14,6 +14,8 @@ use crate::{ pub(crate) mod lock; pub(super) mod types; +pub use types::Binary; + /// A trait for borrowing binary data from JavaScript values /// /// Provides both statically and dynamically checked borrowing. Mutable borrows diff --git a/crates/neon/src/types/buffer/types.rs b/crates/neon/src/types/buffer/types.rs index dd7813367..27cc5fc43 100644 --- a/crates/neon/src/types/buffer/types.rs +++ b/crates/neon/src/types/buffer/types.rs @@ -13,6 +13,24 @@ use crate::{ }; /// The Node [`Buffer`](https://nodejs.org/api/buffer.html) type. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn make_sequence(mut cx: FunctionContext) -> JsResult { +/// let len = cx.argument::(0)?.value(&mut cx); +/// let mut buffer = cx.buffer(len as usize)?; +/// +/// for (i, elem) in buffer.as_mut_slice(&mut cx).iter_mut().enumerate() { +/// *elem = i as u8; +/// } +/// +/// Ok(buffer) +/// } +/// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsBuffer(raw::Local); @@ -137,6 +155,24 @@ impl TypedArray for JsBuffer { } /// The standard JS [`ArrayBuffer`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) type. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn make_sequence(mut cx: FunctionContext) -> JsResult { +/// let len = cx.argument::(0)?.value(&mut cx); +/// let mut buffer = cx.array_buffer(len as usize)?; +/// +/// for (i, elem) in buffer.as_mut_slice(&mut cx).iter_mut().enumerate() { +/// *elem = i as u8; +/// } +/// +/// Ok(buffer) +/// } +/// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsArrayBuffer(raw::Local); @@ -241,17 +277,111 @@ impl TypedArray for JsArrayBuffer { } } -/// The standard JS [`TypedArray`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) type. +pub trait Binary: private::Sealed + Copy { + fn raw() -> TypedArrayType; +} + +/// The family of JS [typed array][typed-arrays] types. +/// +/// ## Typed Arrays +/// +/// JavaScript's [typed arrays][typed-arrays] are objects that allow efficiently reading +/// and writing raw binary data in memory. In Neon, the generic type `JsTypedArray` +/// represents a JavaScript typed array with element type `T`. For example, a JavaScript +/// [`Uint32Array`][Uint32Array] represents a compact array of 32-bit unsigned integers, +/// and is represented in Neon as a `JsTypedArray`. +/// +/// Neon also offers a set of convenience shorthands for concrete instances of +/// `JsTypedArray`, named after their corresponding JavaScript type. For example, +/// `JsTypedArray` can also be referred to as [`JsUint32Array`][JsUint32Array]. +/// +/// The following table shows the complete set of typed array types, with both their +/// JavaScript and Neon types: +/// +/// | Rust Type | Convenience Type | JavaScript Type | +/// | ------------------------------ | -------------------------------------- | ---------------------------------- | +/// | `JsTypedArray<`[`u8`][u8]`>` | [`JsUint8Array`][JsUint8Array] | [`Uint8Array`][Uint8Array] | +/// | `JsTypedArray<`[`i8`][i8]`>` | [`JsInt8Array`][JsInt8Array] | [`Int8Array`][Int8Array] | +/// | `JsTypedArray<`[`u16`][u16]`>` | [`JsUint16Array`][JsUint16Array] | [`Uint16Array`][Uint16Array] | +/// | `JsTypedArray<`[`i16`][i16]`>` | [`JsInt16Array`][JsInt16Array] | [`Int16Array`][Int16Array] | +/// | `JsTypedArray<`[`u32`][u32]`>` | [`JsUint32Array`][JsUint32Array] | [`Uint32Array`][Uint32Array] | +/// | `JsTypedArray<`[`i32`][i32]`>` | [`JsInt32Array`][JsInt32Array] | [`Int32Array`][Int32Array] | +/// | `JsTypedArray<`[`u64`][u64]`>` | [`JsBigUint64Array`][JsBigUint64Array] | [`BigUint64Array`][BigUint64Array] | +/// | `JsTypedArray<`[`i64`][i64]`>` | [`JsBigInt64Array`][JsBigInt64Array] | [`BigInt64Array`][BigInt64Array] | +/// | `JsTypedArray<`[`f32`][f32]`>` | [`JsFloat32Array`][JsFloat32Array] | [`Float32Array`][Float32Array] | +/// | `JsTypedArray<`[`f64`][f64]`>` | [`JsFloat64Array`][JsFloat64Array] | [`Float64Array`][Float64Array] | +/// +/// ### Example: Creating an integer array +/// +/// This example creates a typed array of unsigned 32-bit integers with a user-specified +/// length: +/// +/// ``` +/// # use neon::prelude::*; +/// fn create_int_array(mut cx: FunctionContext) -> JsResult> { +/// let len = cx.argument::(0)?.value(&mut cx) as usize; +/// JsTypedArray::new(&mut cx, len) +/// } +/// ``` +/// +/// ## Buffers +/// +/// Typed arrays are managed with the [`ArrayBuffer`][ArrayBuffer] type, which controls +/// the storage of the underlying data buffer, and several typed views for managing access +/// to the buffer. Neon provides access to the `ArrayBuffer` class with the +/// [`JsArrayBuffer`](crate::types::JsArrayBuffer) type. +/// +/// Node also provides a [`Buffer`][Buffer] type, which is built on top of `ArrayBuffer` +/// and provides additional functionality. Neon provides access to the `Buffer` class +/// with the [`JsBuffer`](crate::types::JsBuffer) type. +/// +/// Many of Node's I/O APIs work with these types, and they can also be used for +/// compact in-memory data structures, which can be shared efficiently between +/// JavaScript and Rust without copying. +/// +/// [u8]: std::primitive::u8 +/// [i8]: std::primitive::i8 +/// [u16]: std::primitive::u16 +/// [i16]: std::primitive::i16 +/// [u32]: std::primitive::u32 +/// [i32]: std::primitive::i32 +/// [u64]: std::primitive::u64 +/// [i64]: std::primitive::i64 +/// [f32]: std::primitive::f32 +/// [f64]: std::primitive::f64 +/// [JsUint8Array]: crate::types::JsUint8Array +/// [JsInt8Array]: crate::types::JsInt8Array +/// [JsUint16Array]: crate::types::JsUint16Array +/// [JsInt16Array]: crate::types::JsInt16Array +/// [JsUint32Array]: crate::types::JsUint32Array +/// [JsInt32Array]: crate::types::JsInt32Array +/// [JsBigUint64Array]: crate::types::JsBigUint64Array +/// [JsBigInt64Array]: crate::types::JsBigInt64Array +/// [JsFloat32Array]: crate::types::JsFloat32Array +/// [JsFloat64Array]: crate::types::JsFloat64Array +/// [Uint8Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array +/// [Int8Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array +/// [Uint16Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array +/// [Int16Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array +/// [Uint32Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array +/// [Int32Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array +/// [BigUint64Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigUint64Array +/// [BigInt64Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array +/// [Float32Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array +/// [Float64Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array +/// [typed-arrays]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays +/// [ArrayBuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer +/// [Buffer]: https://nodejs.org/api/buffer.html #[derive(Debug)] #[repr(transparent)] -pub struct JsTypedArray { +pub struct JsTypedArray { local: raw::Local, _type: PhantomData, } -impl private::Sealed for JsTypedArray {} +impl private::Sealed for JsTypedArray {} -unsafe impl TransparentNoCopyWrapper for JsTypedArray { +unsafe impl TransparentNoCopyWrapper for JsTypedArray { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { @@ -259,7 +389,7 @@ unsafe impl TransparentNoCopyWrapper for JsTypedArray { } } -impl Managed for JsTypedArray { +impl Managed for JsTypedArray { fn to_raw(&self) -> raw::Local { self.local } @@ -272,7 +402,7 @@ impl Managed for JsTypedArray { } } -impl TypedArray for JsTypedArray { +impl TypedArray for JsTypedArray { type Item = T; fn as_slice<'cx, 'a, C>(&self, cx: &'a C) -> &'a [Self::Item] @@ -342,8 +472,53 @@ impl TypedArray for JsTypedArray { } } +impl JsTypedArray { + pub fn from_array_buffer<'cx, 'a, C>( + cx: &'a mut C, + buffer: Handle, + byte_offset: usize, + len: usize, + + ) -> JsResult<'cx, Self> + where + C: Context<'cx>, + { + let result = unsafe { + sys::typedarray::new(cx.env().to_raw(), T::raw(), buffer.to_raw(), byte_offset, len) + }; + + if let Ok(arr) = result { + Ok(Handle::new_internal(Self { + local: arr, + _type: PhantomData, + })) + } else { + Err(Throw::new()) + } + } + + pub fn new<'cx, 'a, C>( + cx: &'a mut C, + len: usize, + ) -> JsResult<'cx, Self> + where + C: Context<'cx>, + { + let buffer = cx.array_buffer(len * std::mem::size_of::())?; + Self::from_array_buffer(cx, buffer, 0, len) + } +} + macro_rules! impl_typed_array { - ($name:expr, $typ:ty, $($pattern:pat)|+$(,)?) => { + ($name:expr, $typ:ty, $($pattern:pat)|+, $tag:ident$(,)?) => { + impl private::Sealed for $typ {} + + impl Binary for $typ { + fn raw() -> TypedArrayType { + TypedArrayType::$tag + } + } + impl Value for JsTypedArray<$typ> {} impl Object for JsTypedArray<$typ> {} @@ -369,17 +544,218 @@ macro_rules! impl_typed_array { }; } -impl_typed_array!("Int8Array", i8, TypedArrayType::I8); +impl_typed_array!("Int8Array", i8, TypedArrayType::I8, I8); impl_typed_array!( "Uint8Array", u8, TypedArrayType::U8 | TypedArrayType::U8Clamped, + U8, ); -impl_typed_array!("Int16Array", i16, TypedArrayType::I16); -impl_typed_array!("Uint16Array", u16, TypedArrayType::U16); -impl_typed_array!("Int32Array", i32, TypedArrayType::I32); -impl_typed_array!("Uint32Array", u32, TypedArrayType::U32); -impl_typed_array!("Float32Array", f32, TypedArrayType::F32); -impl_typed_array!("Float64Array", f64, TypedArrayType::F64); -impl_typed_array!("BigInt64Array", i64, TypedArrayType::I64); -impl_typed_array!("BigUint64Array", u64, TypedArrayType::U64); +impl_typed_array!("Int16Array", i16, TypedArrayType::I16, I16); +impl_typed_array!("Uint16Array", u16, TypedArrayType::U16, U16); +impl_typed_array!("Int32Array", i32, TypedArrayType::I32, I32); +impl_typed_array!("Uint32Array", u32, TypedArrayType::U32, U32); +impl_typed_array!("Float32Array", f32, TypedArrayType::F32, F32); +impl_typed_array!("Float64Array", f64, TypedArrayType::F64, F64); +impl_typed_array!("BigInt64Array", i64, TypedArrayType::I64, I64); +impl_typed_array!("BigUint64Array", u64, TypedArrayType::U64, U64); + +/// The standard JS [`Int8Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int8Array) type. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn double(mut cx: FunctionContext) -> JsResult { +/// let mut array: Handle = cx.argument(0)?; +/// +/// for elem in array.as_mut_slice(&mut cx).iter_mut() { +/// *elem *= 2; +/// } +/// +/// Ok(cx.undefined()) +/// } +/// ``` +pub type JsInt8Array = JsTypedArray; + +/// The standard JS [`Uint8Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) type. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn double(mut cx: FunctionContext) -> JsResult { +/// let mut array: Handle = cx.argument(0)?; +/// +/// for elem in array.as_mut_slice(&mut cx).iter_mut() { +/// *elem *= 2; +/// } +/// +/// Ok(cx.undefined()) +/// } +/// ``` +pub type JsUint8Array = JsTypedArray; + +/// The standard JS [`Int16Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int16Array) type. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn double(mut cx: FunctionContext) -> JsResult { +/// let mut array: Handle = cx.argument(0)?; +/// +/// for elem in array.as_mut_slice(&mut cx).iter_mut() { +/// *elem *= 2; +/// } +/// +/// Ok(cx.undefined()) +/// } +/// ``` +pub type JsInt16Array = JsTypedArray; + +/// The standard JS [`Uint16Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array) type. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn double(mut cx: FunctionContext) -> JsResult { +/// let mut array: Handle = cx.argument(0)?; +/// +/// for elem in array.as_mut_slice(&mut cx).iter_mut() { +/// *elem *= 2; +/// } +/// +/// Ok(cx.undefined()) +/// } +/// ``` +pub type JsUint16Array = JsTypedArray; + +/// The standard JS [`Int32Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int32Array) type. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn double(mut cx: FunctionContext) -> JsResult { +/// let mut array: Handle = cx.argument(0)?; +/// +/// for elem in array.as_mut_slice(&mut cx).iter_mut() { +/// *elem *= 2; +/// } +/// +/// Ok(cx.undefined()) +/// } +/// ``` +pub type JsInt32Array = JsTypedArray; + +/// The standard JS [`Uint32Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array) type. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn double(mut cx: FunctionContext) -> JsResult { +/// let mut array: Handle = cx.argument(0)?; +/// +/// for elem in array.as_mut_slice(&mut cx).iter_mut() { +/// *elem *= 2; +/// } +/// +/// Ok(cx.undefined()) +/// } +/// ``` +pub type JsUint32Array = JsTypedArray; + +/// The standard JS [`BigInt64Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array) type. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn double(mut cx: FunctionContext) -> JsResult { +/// let mut array: Handle = cx.argument(0)?; +/// +/// for elem in array.as_mut_slice(&mut cx).iter_mut() { +/// *elem *= 2; +/// } +/// +/// Ok(cx.undefined()) +/// } +/// ``` +pub type JsBigInt64Array = JsTypedArray; + +/// The standard JS [`BigUint64Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/BigUint64Array) type. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn double(mut cx: FunctionContext) -> JsResult { +/// let mut array: Handle = cx.argument(0)?; +/// +/// for elem in array.as_mut_slice(&mut cx).iter_mut() { +/// *elem *= 2; +/// } +/// +/// Ok(cx.undefined()) +/// } +/// ``` +pub type JsBigUint64Array = JsTypedArray; + +/// The standard JS [`Float32Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Float32Array) type. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn double(mut cx: FunctionContext) -> JsResult { +/// let mut array: Handle = cx.argument(0)?; +/// +/// for elem in array.as_mut_slice(&mut cx).iter_mut() { +/// *elem *= 2.0; +/// } +/// +/// Ok(cx.undefined()) +/// } +/// ``` +pub type JsFloat32Array = JsTypedArray; + +/// The standard JS [`Float64Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Float64Array) type. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn double(mut cx: FunctionContext) -> JsResult { +/// let mut array: Handle = cx.argument(0)?; +/// +/// for elem in array.as_mut_slice(&mut cx).iter_mut() { +/// *elem *= 2.0; +/// } +/// +/// Ok(cx.undefined()) +/// } +/// ``` +pub type JsFloat64Array = JsTypedArray; diff --git a/crates/neon/src/types/mod.rs b/crates/neon/src/types/mod.rs index fae10e5ea..412332c7a 100644 --- a/crates/neon/src/types/mod.rs +++ b/crates/neon/src/types/mod.rs @@ -62,7 +62,8 @@ //! getting and setting properties. //! - **Standard object types:** [`JsFunction`](JsFunction), [`JsArray`](JsArray), //! [`JsDate`](JsDate), and [`JsError`](JsError). -//! - **Typed arrays:** [`JsBuffer`](JsBuffer) and [`JsArrayBuffer`](JsArrayBuffer). +//! - **Typed arrays:** [`JsBuffer`](JsBuffer), [`JsArrayBuffer`](JsArrayBuffer), +//! and [`JsTypedArray`](JsTypedArray). //! - **Custom types:** [`JsBox`](JsBox), a special Neon type that allows the creation //! of custom objects that own Rust data structures. //! - **Primitive types:** These are the built-in JavaScript datatypes that are not @@ -108,7 +109,11 @@ use crate::{ pub use self::{ boxed::{Finalize, JsBox}, - buffer::types::{JsArrayBuffer, JsBuffer, JsTypedArray}, + buffer::types::{ + JsArrayBuffer, JsBuffer, JsBigInt64Array, JsBigUint64Array, JsFloat32Array, + JsFloat64Array, JsInt8Array, JsInt16Array, JsInt32Array, JsTypedArray, + JsUint8Array, JsUint16Array, JsUint32Array, + }, error::JsError, promise::{Deferred, JsPromise}, }; diff --git a/doc/types.jpg b/doc/types.jpg index b86beb81774ca7be34e55b3b2c268e8cf643ee3f..74a6f6af51c9092039710bde5d07a0d25f0308a4 100644 GIT binary patch literal 55021 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

fHcKugRLjJ4eEA)m; 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 literal 29464 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 z zx&Ly%|9t%=MI6 zkcf$XR&`a0X!{?Qz&livdXAF}Y>9JQtNlKU#~V z@f?$8bKk;?y=3RTgQi>jhYKQB?{D`v>prxR(piG{)v|#<_VLGf9Z7K7vdA>Bbc`#i zmZM*W6Z89PLb{8bd({P+i_fC z96eLS>Y1AfbjlV`2RFrcbQQ1BELa-_ouWOC7AcpW(4O?Yq-ySfA&#t9eoy1NBM z^`G%ghV|VA1iIY$a7Fi3^#8>0*O$0gUY$9_ETINV+28mfn(OazUKxgK|7L83UWf=qmx=NjjO)(ZINpE^vEJ-sd2)6Cp_tJ(K^K-d?Ewq>}RbXX5Kgqtt zey)?<7FYNP0`rbTzHk-Sx3$D-@a5T#(YdKso9hFN0@dx!-)!pf%0Ad2rZnWlx})i$ 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 diff --git a/doc/types.pptx b/doc/types.pptx index 8d6a4946be6bc4b11acd63437b8fbe856141cb7c..7faff8c023f296ea52aabe5262195decb6e5f5c5 100644 GIT binary patch delta 19610 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 wtRN4A4+&vfBU#T#DAH(vOqih{yVREek6H delta 18497 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 diff --git a/test/napi/lib/objects.js b/test/napi/lib/objects.js index 3df0e5c6d..22a4a1e2c 100644 --- a/test/napi/lib/objects.js +++ b/test/napi/lib/objects.js @@ -200,6 +200,64 @@ describe("JsObject", function () { assert.strictEqual(Buffer.from(buf).toString(), expected); }); + it("gets a typed array constructed from an ArrayBuffer", function () { + var b = new ArrayBuffer(64); + var i8 = addon.return_int8array_from_arraybuffer(b); + assert.strictEqual(i8.byteLength, 64); + assert.strictEqual(i8.length, 64); + i8[0] = 0x17; + i8[1] = -0x17; + assert.deepEqual([...i8.slice(0, 2)], [0x17, -0x17]); + + var b = new ArrayBuffer(64); + var i16 = addon.return_int16array_from_arraybuffer(b); + assert.strictEqual(i16.byteLength, 64); + assert.strictEqual(i16.length, 32); + i16[0] = 0x1234; + i16[1] = -1; + i16[2] = -2; + i16[3] = 0x5678; + assert.deepEqual([...i16.slice(0, 4)], [0x1234, -1, -2, 0x5678]); + var u8 = new Uint8Array(b); + assert.deepEqual([...u8.slice(0, 8)], [0x34, 0x12, 0xff, 0xff, 0xfe, 0xff, 0x78, 0x56]); + + var b = new ArrayBuffer(64); + var u32 = addon.return_uint32array_from_arraybuffer(b); + assert.strictEqual(u32.byteLength, 64); + assert.strictEqual(u32.length, 16); + u32[0] = 0x12345678; + var u8 = new Uint8Array(b); + assert.deepEqual([...u8.slice(0, 4)], [0x78, 0x56, 0x34, 0x12]); + + var b = new ArrayBuffer(64); + var f64 = addon.return_float64array_from_arraybuffer(b); + assert.strictEqual(f64.byteLength, 64); + assert.strictEqual(f64.length, 8); + f64[0] = 1.0; + f64[1] = 2.0; + f64[2] = 3.141592653589793; + assert.deepEqual([...f64.slice(0, 3)], [1.0, 2.0, 3.141592653589793]); + assert.deepEqual([...(new Float64Array(b)).slice(0, 3)], [1.0, 2.0, 3.141592653589793]); + + var b = new ArrayBuffer(64); + var u64 = addon.return_biguint64array_from_arraybuffer(b); + assert.strictEqual(u64.byteLength, 64); + assert.strictEqual(u64.length, 8); + u64[0] = 0x1234567887654321n; + u64[1] = 0xcafed00d1337c0den; + var u8 = new Uint8Array(b); + assert.deepEqual([...u64.slice(0, 2)], [0x1234567887654321n, 0xcafed00d1337c0den]); + assert.deepEqual([...u8.slice(0, 16)], [0x21, 0x43, 0x65, 0x87, 0x78, 0x56, 0x34, 0x12, 0xde, 0xc0, 0x37, 0x13, 0x0d, 0xd0, 0xfe, 0xca]); + }); + + it("gets a new typed array", function () { + var i32 = addon.return_new_int32array(16); + assert.strictEqual(i32.constructor, Int32Array); + assert.strictEqual(i32.byteLength, 64); + assert.strictEqual(i32.length, 16); + assert.deepEqual([...i32], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + }); + it("correctly reads a Buffer using the lock API", function () { var b = Buffer.allocUnsafe(16); b.writeUInt8(147, 0); diff --git a/test/napi/src/js/objects.rs b/test/napi/src/js/objects.rs index f1c9409c5..5b6c5550c 100644 --- a/test/napi/src/js/objects.rs +++ b/test/napi/src/js/objects.rs @@ -167,6 +167,41 @@ pub fn return_external_array_buffer(mut cx: FunctionContext) -> JsResult JsResult { + let buf = cx.argument::(0)?; + let len = buf.as_slice(&cx).len(); + JsInt8Array::from_array_buffer(&mut cx, buf, 0, len) +} + +pub fn return_int16array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + let len = buf.as_slice(&cx).len(); + JsInt16Array::from_array_buffer(&mut cx, buf, 0, len / 2) +} + +pub fn return_uint32array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + let len = buf.as_slice(&cx).len(); + JsUint32Array::from_array_buffer(&mut cx, buf, 0, len / 4) +} + +pub fn return_float64array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + let len = buf.as_slice(&cx).len(); + JsFloat64Array::from_array_buffer(&mut cx, buf, 0, len / 8) +} + +pub fn return_biguint64array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + let len = buf.as_slice(&cx).len(); + JsBigUint64Array::from_array_buffer(&mut cx, buf, 0, len / 8) +} + +pub fn return_new_int32array(mut cx: FunctionContext) -> JsResult { + let len = cx.argument::(0)?.value(&mut cx) as usize; + JsInt32Array::new(&mut cx, len) +} + pub fn read_buffer_with_lock(mut cx: FunctionContext) -> JsResult { let b: Handle = cx.argument(0)?; let i = cx.argument::(1)?.value(&mut cx) as usize; diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index e35315fb0..b4cf7e11e 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -235,6 +235,12 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { cx.export_function("return_buffer", return_buffer)?; cx.export_function("return_external_buffer", return_external_buffer)?; cx.export_function("return_external_array_buffer", return_external_array_buffer)?; + cx.export_function("return_int8array_from_arraybuffer", return_int8array_from_arraybuffer)?; + cx.export_function("return_int16array_from_arraybuffer", return_int16array_from_arraybuffer)?; + cx.export_function("return_uint32array_from_arraybuffer", return_uint32array_from_arraybuffer)?; + cx.export_function("return_float64array_from_arraybuffer", return_float64array_from_arraybuffer)?; + cx.export_function("return_biguint64array_from_arraybuffer", return_biguint64array_from_arraybuffer)?; + cx.export_function("return_new_int32array", return_new_int32array)?; cx.export_function("read_buffer_with_lock", read_buffer_with_lock)?; cx.export_function("read_buffer_with_borrow", read_buffer_with_borrow)?; cx.export_function("write_buffer_with_lock", write_buffer_with_lock)?; From 6d136af41093c2409c762082882438fd5f7bd880 Mon Sep 17 00:00:00 2001 From: David Herman Date: Wed, 29 Jun 2022 19:46:33 -0700 Subject: [PATCH 02/31] - Use aquamarine crate to product the type hierarchy diagram in neon::types API docs - Delete the PowerPoint-generated digram - Refactor the module tree slightly, in order to wrap the module docs with `#[aquamarine]` attribute - Flesh out the remaining blank spots in the typed array API docs - Change the `Binary` trait to use an associated constant instead of a nullary method --- crates/neon/Cargo.toml | 1 + crates/neon/src/lib.rs | 8 +- crates/neon/src/types_docs.rs | 106 ++++++++++++++++++ .../neon/src/{types => types_impl}/boxed.rs | 0 .../src/{types => types_impl}/buffer/lock.rs | 0 .../src/{types => types_impl}/buffer/mod.rs | 28 ++++- .../src/{types => types_impl}/buffer/types.rs | 15 ++- crates/neon/src/{types => types_impl}/date.rs | 0 .../neon/src/{types => types_impl}/error.rs | 3 +- .../src/{types => types_impl}/function/mod.rs | 0 .../{types => types_impl}/function/private.rs | 0 crates/neon/src/{types => types_impl}/mod.rs | 75 +------------ .../neon/src/{types => types_impl}/private.rs | 3 +- .../neon/src/{types => types_impl}/promise.rs | 5 +- crates/neon/src/{types => types_impl}/utf8.rs | 0 doc/types.jpg | Bin 55021 -> 0 bytes doc/types.pptx | Bin 45370 -> 0 bytes 17 files changed, 156 insertions(+), 88 deletions(-) create mode 100644 crates/neon/src/types_docs.rs rename crates/neon/src/{types => types_impl}/boxed.rs (100%) rename crates/neon/src/{types => types_impl}/buffer/lock.rs (100%) rename crates/neon/src/{types => types_impl}/buffer/mod.rs (83%) rename crates/neon/src/{types => types_impl}/buffer/types.rs (98%) rename crates/neon/src/{types => types_impl}/date.rs (100%) rename crates/neon/src/{types => types_impl}/error.rs (97%) rename crates/neon/src/{types => types_impl}/function/mod.rs (100%) rename crates/neon/src/{types => types_impl}/function/private.rs (100%) rename crates/neon/src/{types => types_impl}/mod.rs (86%) rename crates/neon/src/{types => types_impl}/private.rs (91%) rename crates/neon/src/{types => types_impl}/promise.rs (98%) rename crates/neon/src/{types => types_impl}/utf8.rs (100%) delete mode 100644 doc/types.jpg delete mode 100644 doc/types.pptx diff --git a/crates/neon/Cargo.toml b/crates/neon/Cargo.toml index 718ad4952..dbae50c9b 100644 --- a/crates/neon/Cargo.toml +++ b/crates/neon/Cargo.toml @@ -25,6 +25,7 @@ semver = "1" smallvec = "1.4.2" once_cell = "1.10.0" neon-macros = { version = "=1.0.0-alpha.1", path = "../neon-macros" } +aquamarine = "0.1.11" [dependencies.tokio] version = "1.18.2" diff --git a/crates/neon/src/lib.rs b/crates/neon/src/lib.rs index f34d75744..dd1f9d0d7 100644 --- a/crates/neon/src/lib.rs +++ b/crates/neon/src/lib.rs @@ -89,7 +89,13 @@ pub mod result; mod sys; #[cfg(feature = "napi-6")] pub mod thread; -pub mod types; +// To use the #[aquamarine] attribute on the top-level neon::types module docs, we have to +// use this hack so we can keep the module docs in a separate file. +// See: https://github.com/mersinvald/aquamarine/issues/5#issuecomment-1168816499 +mod types_docs; +mod types_impl; + +pub use types_docs::exports as types; #[doc(hidden)] pub mod macro_internal; diff --git a/crates/neon/src/types_docs.rs b/crates/neon/src/types_docs.rs new file mode 100644 index 000000000..074ec0c13 --- /dev/null +++ b/crates/neon/src/types_docs.rs @@ -0,0 +1,106 @@ +#[cfg_attr(doc, aquamarine::aquamarine)] +/// Representations of JavaScript's core builtin types. +/// +/// ## Modeling JavaScript Types +/// +/// All JavaScript values in Neon implement the abstract [`Value`] trait, which +/// is the most generic way to work with JavaScript values. Neon provides a +/// number of types that implement this trait, each representing a particular +/// type of JavaScript value. +/// +/// By convention, JavaScript types in Neon have the prefix `Js` in their name, +/// such as [`JsNumber`](crate::types::JsNumber) (for the JavaScript `number` +/// type) or [`JsFunction`](crate::types::JsFunction) (for the JavaScript +/// `function` type). +/// +/// ### Handles and Casts +/// +/// Access to JavaScript values in Neon works through [handles](crate::handle), +/// which ensure the safe interoperation between Rust and the JavaScript garbage +/// collector. This means, for example, a Rust variable that stores a JavaScript string +/// will have the type `Handle` rather than [`JsString`](crate::types::JsString). +/// +/// Neon types model the JavaScript type hierarchy through the use of *casts*. +/// The [`Handle::upcast()`](crate::handle::Handle::upcast) method safely converts +/// a handle to a JavaScript value of one type into a handle to a value of its +/// supertype. For example, it's safe to treat a [`JsArray`](crate::types::JsArray) +/// as a [`JsObject`](crate::types::JsObject), so you can do an "upcast" and it will +/// never fail: +/// +/// ``` +/// # use neon::prelude::*; +/// fn as_object(array: Handle) -> Handle { +/// let object: Handle = array.upcast(); +/// object +/// } +/// ``` +/// +/// Unlike upcasts, the [`Handle::downcast()`](crate::handle::Handle::downcast) method +/// requires a runtime check to test a value's type at runtime, so it can fail with +/// a [`DowncastError`](crate::handle::DowncastError): +/// +/// ``` +/// # use neon::prelude::*; +/// fn as_array<'a>( +/// cx: &mut impl Context<'a>, +/// object: Handle<'a, JsObject> +/// ) -> JsResult<'a, JsArray> { +/// object.downcast(cx).or_throw(cx) +/// } +/// ``` +/// +/// ### The JavaScript Type Hierarchy +/// +/// ```mermaid +/// flowchart TB +/// JsValue +/// JsValue-->JsObject +/// subgraph primitives [Primitive Types] +/// JsBoolean +/// JsNumber +/// JsString +/// JsNull +/// JsUndefined +/// end +/// subgraph objects [Standard Object Types] +/// JsFunction +/// JsArray +/// JsDate +/// JsError +/// end +/// subgraph typedarrays [Typed Arrays] +/// JsBuffer +/// JsArrayBuffer +/// JsTypedArray["JsTypedArray<T>"] +/// end +/// subgraph custom [Custom Types] +/// JsBox +/// end +/// JsValue-->primitives +/// JsObject-->objects +/// JsObject-->typedarrays +/// JsObject-->custom +/// ``` +/// +/// The JavaScript type hierarchy includes: +/// +/// - [`JsValue`](JsValue): This is the top of the type hierarchy, and can refer to +/// any JavaScript value. (For TypeScript programmers, this can be thought of as +/// similar to TypeScript's [`unknown`][unknown] type.) +/// - [`JsObject`](JsObject): This is the top of the object type hierarchy. Object +/// types all implement the [`Object`](crate::object::Object) trait, which allows +/// getting and setting properties. +/// - **Standard object types:** [`JsFunction`](JsFunction), [`JsArray`](JsArray), +/// [`JsDate`](JsDate), and [`JsError`](JsError). +/// - **Typed arrays:** [`JsBuffer`](JsBuffer), [`JsArrayBuffer`](JsArrayBuffer), +/// and [`JsTypedArray`](JsTypedArray). +/// - **Custom types:** [`JsBox`](JsBox), a special Neon type that allows the creation +/// of custom objects that own Rust data structures. +/// - **Primitive types:** These are the built-in JavaScript datatypes that are not +/// object types: [`JsNumber`](JsNumber), [`JsBoolean`](JsBoolean), +/// [`JsString`](JsString), [`JsNull`](JsNull), and [`JsUndefined`](JsUndefined). +/// +/// [unknown]: https://mariusschulz.com/blog/the-unknown-type-in-typescript#the-unknown-type +pub mod exports { + pub use crate::types_impl::*; +} diff --git a/crates/neon/src/types/boxed.rs b/crates/neon/src/types_impl/boxed.rs similarity index 100% rename from crates/neon/src/types/boxed.rs rename to crates/neon/src/types_impl/boxed.rs diff --git a/crates/neon/src/types/buffer/lock.rs b/crates/neon/src/types_impl/buffer/lock.rs similarity index 100% rename from crates/neon/src/types/buffer/lock.rs rename to crates/neon/src/types_impl/buffer/lock.rs diff --git a/crates/neon/src/types/buffer/mod.rs b/crates/neon/src/types_impl/buffer/mod.rs similarity index 83% rename from crates/neon/src/types/buffer/mod.rs rename to crates/neon/src/types_impl/buffer/mod.rs index 9ab00e387..58affa819 100644 --- a/crates/neon/src/types/buffer/mod.rs +++ b/crates/neon/src/types_impl/buffer/mod.rs @@ -1,3 +1,5 @@ +//! Types and traits for working with binary buffers. + use std::{ cell::RefCell, error::Error, @@ -16,10 +18,30 @@ pub(super) mod types; pub use types::Binary; -/// A trait for borrowing binary data from JavaScript values +/// A trait allowing Rust to borrow binary data from the memory buffer of JavaScript +/// [typed arrays][typed-arrays]. +/// +/// This trait provides both statically and dynamically checked borrowing. As usual +/// in Rust, mutable borrows are guaranteed not to overlap with other borrows. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn double(mut cx: FunctionContext) -> JsResult { +/// let mut array: Handle = cx.argument(0)?; +/// +/// for elem in array.as_mut_slice(&mut cx).iter_mut() { +/// *elem *= 2; +/// } +/// +/// Ok(cx.undefined()) +/// } +/// ``` /// -/// Provides both statically and dynamically checked borrowing. Mutable borrows -/// are guaranteed not to overlap with other borrows. +/// [typed-arrays]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays pub trait TypedArray: private::Sealed { type Item; diff --git a/crates/neon/src/types/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs similarity index 98% rename from crates/neon/src/types/buffer/types.rs rename to crates/neon/src/types_impl/buffer/types.rs index 27cc5fc43..fbf41b5a4 100644 --- a/crates/neon/src/types/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -3,13 +3,14 @@ use std::{marker::PhantomData, slice}; use crate::{ context::{internal::Env, Context}, handle::{internal::TransparentNoCopyWrapper, Handle, Managed}, + object::Object, result::{JsResult, Throw}, sys::{self, raw, TypedArrayType}, types::buffer::{ lock::{Ledger, Lock}, private, BorrowError, Ref, RefMut, TypedArray, }, - types::{private::ValueInternal, Object, Value}, + types::{private::ValueInternal, Value}, }; /// The Node [`Buffer`](https://nodejs.org/api/buffer.html) type. @@ -277,8 +278,12 @@ impl TypedArray for JsArrayBuffer { } } +/// A marker trait for all possible element types of binary buffers. +/// +/// This trait can only be implemented within the Neon library. pub trait Binary: private::Sealed + Copy { - fn raw() -> TypedArrayType; + /// The internal Node-API enum value for this binary type. + const RAW: TypedArrayType; } /// The family of JS [typed array][typed-arrays] types. @@ -484,7 +489,7 @@ impl JsTypedArray { C: Context<'cx>, { let result = unsafe { - sys::typedarray::new(cx.env().to_raw(), T::raw(), buffer.to_raw(), byte_offset, len) + sys::typedarray::new(cx.env().to_raw(), T::RAW, buffer.to_raw(), byte_offset, len) }; if let Ok(arr) = result { @@ -514,9 +519,7 @@ macro_rules! impl_typed_array { impl private::Sealed for $typ {} impl Binary for $typ { - fn raw() -> TypedArrayType { - TypedArrayType::$tag - } + const RAW: TypedArrayType = TypedArrayType::$tag; } impl Value for JsTypedArray<$typ> {} diff --git a/crates/neon/src/types/date.rs b/crates/neon/src/types_impl/date.rs similarity index 100% rename from crates/neon/src/types/date.rs rename to crates/neon/src/types_impl/date.rs diff --git a/crates/neon/src/types/error.rs b/crates/neon/src/types_impl/error.rs similarity index 97% rename from crates/neon/src/types/error.rs rename to crates/neon/src/types_impl/error.rs index bb3386595..e0a67220f 100644 --- a/crates/neon/src/types/error.rs +++ b/crates/neon/src/types_impl/error.rs @@ -5,9 +5,10 @@ use std::panic::{catch_unwind, UnwindSafe}; use crate::{ context::{internal::Env, Context}, handle::{internal::TransparentNoCopyWrapper, Handle, Managed}, + object::Object, result::{NeonResult, Throw}, sys::{self, raw}, - types::{build, private::ValueInternal, utf8::Utf8, Object, Value}, + types::{build, private::ValueInternal, utf8::Utf8, Value}, }; /// A JS `Error` object. diff --git a/crates/neon/src/types/function/mod.rs b/crates/neon/src/types_impl/function/mod.rs similarity index 100% rename from crates/neon/src/types/function/mod.rs rename to crates/neon/src/types_impl/function/mod.rs diff --git a/crates/neon/src/types/function/private.rs b/crates/neon/src/types_impl/function/private.rs similarity index 100% rename from crates/neon/src/types/function/private.rs rename to crates/neon/src/types_impl/function/private.rs diff --git a/crates/neon/src/types/mod.rs b/crates/neon/src/types_impl/mod.rs similarity index 86% rename from crates/neon/src/types/mod.rs rename to crates/neon/src/types_impl/mod.rs index 412332c7a..97095a490 100644 --- a/crates/neon/src/types/mod.rs +++ b/crates/neon/src/types_impl/mod.rs @@ -1,77 +1,4 @@ -//! Representations of JavaScript's core builtin types. -//! -//! ## Modeling JavaScript Types -//! -//! All JavaScript values in Neon implement the abstract [`Value`] trait, which -//! is the most generic way to work with JavaScript values. Neon provides a -//! number of types that implement this trait, each representing a particular -//! type of JavaScript value. -//! -//! By convention, JavaScript types in Neon have the prefix `Js` in their name, -//! such as [`JsNumber`](crate::types::JsNumber) (for the JavaScript `number` -//! type) or [`JsFunction`](crate::types::JsFunction) (for the JavaScript -//! `function` type). -//! -//! ### Handles and Casts -//! -//! Access to JavaScript values in Neon works through [handles](crate::handle), -//! which ensure the safe interoperation between Rust and the JavaScript garbage -//! collector. This means, for example, a Rust variable that stores a JavaScript string -//! will have the type `Handle` rather than [`JsString`](crate::types::JsString). -//! -//! Neon types model the JavaScript type hierarchy through the use of *casts*. -//! The [`Handle::upcast()`](crate::handle::Handle::upcast) method safely converts -//! a handle to a JavaScript value of one type into a handle to a value of its -//! supertype. For example, it's safe to treat a [`JsArray`](crate::types::JsArray) -//! as a [`JsObject`](crate::types::JsObject), so you can do an "upcast" and it will -//! never fail: -//! -//! ``` -//! # use neon::prelude::*; -//! fn as_object(array: Handle) -> Handle { -//! let object: Handle = array.upcast(); -//! object -//! } -//! ``` -//! -//! Unlike upcasts, the [`Handle::downcast()`](crate::handle::Handle::downcast) method -//! requires a runtime check to test a value's type at runtime, so it can fail with -//! a [`DowncastError`](crate::handle::DowncastError): -//! -//! ``` -//! # use neon::prelude::*; -//! fn as_array<'a>( -//! cx: &mut impl Context<'a>, -//! object: Handle<'a, JsObject> -//! ) -> JsResult<'a, JsArray> { -//! object.downcast(cx).or_throw(cx) -//! } -//! ``` -//! -//! ### The JavaScript Type Hierarchy -//! -//! ![The Neon type hierarchy, described in detail below.][types] -//! -//! The JavaScript type hierarchy includes: -//! -//! - [`JsValue`](JsValue): This is the top of the type hierarchy, and can refer to -//! any JavaScript value. (For TypeScript programmers, this can be thought of as -//! similar to TypeScript's [`unknown`][unknown] type.) -//! - [`JsObject`](JsObject): This is the top of the object type hierarchy. Object -//! types all implement the [`Object`](crate::object::Object) trait, which allows -//! getting and setting properties. -//! - **Standard object types:** [`JsFunction`](JsFunction), [`JsArray`](JsArray), -//! [`JsDate`](JsDate), and [`JsError`](JsError). -//! - **Typed arrays:** [`JsBuffer`](JsBuffer), [`JsArrayBuffer`](JsArrayBuffer), -//! and [`JsTypedArray`](JsTypedArray). -//! - **Custom types:** [`JsBox`](JsBox), a special Neon type that allows the creation -//! of custom objects that own Rust data structures. -//! - **Primitive types:** These are the built-in JavaScript datatypes that are not -//! object types: [`JsNumber`](JsNumber), [`JsBoolean`](JsBoolean), -//! [`JsString`](JsString), [`JsNull`](JsNull), and [`JsUndefined`](JsUndefined). -//! -//! [types]: https://raw.githubusercontent.com/neon-bindings/neon/main/doc/types.jpg -//! [unknown]: https://mariusschulz.com/blog/the-unknown-type-in-typescript#the-unknown-type +// See types_docs.rs for top-level module API docs. pub(crate) mod boxed; pub mod buffer; diff --git a/crates/neon/src/types/private.rs b/crates/neon/src/types_impl/private.rs similarity index 91% rename from crates/neon/src/types/private.rs rename to crates/neon/src/types_impl/private.rs index 996ba4564..f5f687e60 100644 --- a/crates/neon/src/types/private.rs +++ b/crates/neon/src/types_impl/private.rs @@ -1,7 +1,8 @@ use crate::{ context::internal::Env, + handle::{Handle, Managed}, sys::raw, - types::{Handle, Managed, Value}, + types::Value, }; pub trait ValueInternal: Managed + 'static { diff --git a/crates/neon/src/types/promise.rs b/crates/neon/src/types_impl/promise.rs similarity index 98% rename from crates/neon/src/types/promise.rs rename to crates/neon/src/types_impl/promise.rs index ef413d864..a6ccb4c2b 100644 --- a/crates/neon/src/types/promise.rs +++ b/crates/neon/src/types_impl/promise.rs @@ -2,10 +2,11 @@ use std::ptr; use crate::{ context::{internal::Env, Context}, - handle::{internal::TransparentNoCopyWrapper, Managed}, + handle::{Handle, internal::TransparentNoCopyWrapper, Managed}, + object::Object, result::JsResult, sys::{self, no_panic::FailureBoundary, raw}, - types::{private::ValueInternal, Handle, Object, Value}, + types::{private::ValueInternal, Value}, }; #[cfg(feature = "napi-4")] diff --git a/crates/neon/src/types/utf8.rs b/crates/neon/src/types_impl/utf8.rs similarity index 100% rename from crates/neon/src/types/utf8.rs rename to crates/neon/src/types_impl/utf8.rs diff --git a/doc/types.jpg b/doc/types.jpg deleted file mode 100644 index 74a6f6af51c9092039710bde5d07a0d25f0308a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55021 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

fHcKugRLjJ4eEA)m; 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 diff --git a/doc/types.pptx b/doc/types.pptx deleted file mode 100644 index 7faff8c023f296ea52aabe5262195decb6e5f5c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45370 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 From a50522eb5f187685123354c9d08d80bf75ec015c Mon Sep 17 00:00:00 2001 From: David Herman Date: Thu, 30 Jun 2022 10:39:31 -0700 Subject: [PATCH 03/31] Types diagram cosmetics: - change from top-to-bottom to left-to-right - make rectangles rounded --- crates/neon/src/types_docs.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/neon/src/types_docs.rs b/crates/neon/src/types_docs.rs index 074ec0c13..a3833e3a6 100644 --- a/crates/neon/src/types_docs.rs +++ b/crates/neon/src/types_docs.rs @@ -52,29 +52,29 @@ /// ### The JavaScript Type Hierarchy /// /// ```mermaid -/// flowchart TB -/// JsValue -/// JsValue-->JsObject +/// flowchart LR +/// JsValue(JsValue) +/// JsValue-->JsObject(JsObject) /// subgraph primitives [Primitive Types] -/// JsBoolean -/// JsNumber -/// JsString -/// JsNull -/// JsUndefined +/// JsBoolean(JsBoolean) +/// JsNumber(JsNumber) +/// JsString(JsString) +/// JsNull(JsNull) +/// JsUndefined(JsUndefined) /// end /// subgraph objects [Standard Object Types] -/// JsFunction -/// JsArray -/// JsDate -/// JsError +/// JsFunction(JsFunction) +/// JsArray(JsArray) +/// JsDate(JsDate) +/// JsError(JsError) /// end /// subgraph typedarrays [Typed Arrays] -/// JsBuffer -/// JsArrayBuffer -/// JsTypedArray["JsTypedArray<T>"] +/// JsBuffer(JsBuffer) +/// JsArrayBuffer(JsArrayBuffer) +/// JsTypedArray("JsTypedArray<T>") /// end /// subgraph custom [Custom Types] -/// JsBox +/// JsBox(JsBox) /// end /// JsValue-->primitives /// JsObject-->objects From 04f122e7cc06be967684085a6121f7cc7d0ed055 Mon Sep 17 00:00:00 2001 From: David Herman Date: Thu, 30 Jun 2022 15:23:38 -0700 Subject: [PATCH 04/31] - Divide type hierarchy diagram into two diagrams and add some more copy around them - Turn every node in the diagrams into a link to the relevant type's API docs --- crates/neon/src/types_docs.rs | 71 +++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/crates/neon/src/types_docs.rs b/crates/neon/src/types_docs.rs index a3833e3a6..b58bbfed4 100644 --- a/crates/neon/src/types_docs.rs +++ b/crates/neon/src/types_docs.rs @@ -3,7 +3,7 @@ /// /// ## Modeling JavaScript Types /// -/// All JavaScript values in Neon implement the abstract [`Value`] trait, which +/// All JavaScript values in Neon implement the abstract [`Value`](Value) trait, which /// is the most generic way to work with JavaScript values. Neon provides a /// number of types that implement this trait, each representing a particular /// type of JavaScript value. @@ -51,54 +51,87 @@ /// /// ### The JavaScript Type Hierarchy /// +/// The top of the JavaScript type hierarchy is modeled with the Neon type +/// [`JsValue`](JsValue). A [handle](crate::handle) to a `JsValue` can refer +/// to any JavaScript value. (For TypeScript programmers, this can be thought +/// of as similar to TypeScript's [`unknown`][unknown] type.) +/// +/// From there, the type hierarchy divides into _object types_ and _primitive +/// types_: +/// /// ```mermaid /// flowchart LR /// JsValue(JsValue) /// JsValue-->JsObject(JsObject) +/// click JsValue "./struct.JsValue.html" "JsValue" +/// click JsObject "./struct.JsObject.html" "JsObject" /// subgraph primitives [Primitive Types] /// JsBoolean(JsBoolean) /// JsNumber(JsNumber) /// JsString(JsString) /// JsNull(JsNull) /// JsUndefined(JsUndefined) +/// click JsBoolean "./struct.JsBoolean.html" "JsBoolean" +/// click JsNumber "./struct.JsNumber.html" "JsNumber" +/// click JsString "./struct.JsString.html" "JsString" +/// click JsNull "./struct.JsNull.html" "JsNull" +/// click JsUndefined "./struct.JsUndefined.html" "JsUndefined" /// end +/// JsValue-->primitives +/// ``` +/// +/// The top of the object type hierarchy is [`JsObject`](JsObject). A handle to a +/// `JsObject` can refer to any JavaScript object. +/// +/// The primitive types are the built-in JavaScript datatypes that are not object +/// types: [`JsBoolean`](JsBoolean), [`JsNumber`](JsNumber), [`JsString`](JsString), +/// [`JsNull`](JsNull), and [`JsUndefined`](JsUndefined). +/// +/// #### Object Types +/// +/// The object type hierarchy further divides into a number of different subtypes: +/// +/// ```mermaid +/// flowchart LR +/// JsObject(JsObject) +/// click JsObject "./struct.JsObject.html" "JsObject" /// subgraph objects [Standard Object Types] /// JsFunction(JsFunction) /// JsArray(JsArray) /// JsDate(JsDate) /// JsError(JsError) +/// click JsFunction "./struct.JsFunction.html" "JsFunction" +/// click JsArray "./struct.JsArray.html" "JsArray" +/// click JsDate "./struct.JsDate.html" "JsDate" +/// click JsError "./struct.JsError.html" "JsError" /// end /// subgraph typedarrays [Typed Arrays] /// JsBuffer(JsBuffer) /// JsArrayBuffer(JsArrayBuffer) /// JsTypedArray("JsTypedArray<T>") +/// click JsBuffer "./struct.JsBuffer.html" "JsBuffer" +/// click JsArrayBuffer "./struct.JsArrayBuffer.html" "JsArrayBuffer" +/// click JsTypedArray "./struct.JsTypedArray.html" "JsTypedArray" /// end /// subgraph custom [Custom Types] /// JsBox(JsBox) +/// click JsBox "./struct.JsBox.html" "JsBox" /// end -/// JsValue-->primitives /// JsObject-->objects /// JsObject-->typedarrays /// JsObject-->custom /// ``` /// -/// The JavaScript type hierarchy includes: -/// -/// - [`JsValue`](JsValue): This is the top of the type hierarchy, and can refer to -/// any JavaScript value. (For TypeScript programmers, this can be thought of as -/// similar to TypeScript's [`unknown`][unknown] type.) -/// - [`JsObject`](JsObject): This is the top of the object type hierarchy. Object -/// types all implement the [`Object`](crate::object::Object) trait, which allows -/// getting and setting properties. -/// - **Standard object types:** [`JsFunction`](JsFunction), [`JsArray`](JsArray), -/// [`JsDate`](JsDate), and [`JsError`](JsError). -/// - **Typed arrays:** [`JsBuffer`](JsBuffer), [`JsArrayBuffer`](JsArrayBuffer), -/// and [`JsTypedArray`](JsTypedArray). -/// - **Custom types:** [`JsBox`](JsBox), a special Neon type that allows the creation -/// of custom objects that own Rust data structures. -/// - **Primitive types:** These are the built-in JavaScript datatypes that are not -/// object types: [`JsNumber`](JsNumber), [`JsBoolean`](JsBoolean), -/// [`JsString`](JsString), [`JsNull`](JsNull), and [`JsUndefined`](JsUndefined). +/// These include several categories of object types: +/// - **Standard object types:** [`JsFunction`](JsFunction), [`JsArray`](JsArray), +/// [`JsDate`](JsDate), and [`JsError`](JsError). +/// - **Typed arrays:** [`JsBuffer`](JsBuffer), [`JsArrayBuffer`](JsArrayBuffer), +/// and [`JsTypedArray`](JsTypedArray). +/// - **Custom types:** [`JsBox`](JsBox), a special Neon type that allows the creation +/// of custom objects that own Rust data structures. +/// +/// All object types implement the [`Object`](crate::object::Object) trait, which +/// allows getting and setting properties of an object. /// /// [unknown]: https://mariusschulz.com/blog/the-unknown-type-in-typescript#the-unknown-type pub mod exports { From 321bb685647d4443f7452ed95b023c0c045bad8b Mon Sep 17 00:00:00 2001 From: David Herman Date: Thu, 30 Jun 2022 17:29:21 -0700 Subject: [PATCH 05/31] Prettier --- test/napi/lib/objects.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/test/napi/lib/objects.js b/test/napi/lib/objects.js index 22a4a1e2c..1132b50a0 100644 --- a/test/napi/lib/objects.js +++ b/test/napi/lib/objects.js @@ -219,7 +219,10 @@ describe("JsObject", function () { i16[3] = 0x5678; assert.deepEqual([...i16.slice(0, 4)], [0x1234, -1, -2, 0x5678]); var u8 = new Uint8Array(b); - assert.deepEqual([...u8.slice(0, 8)], [0x34, 0x12, 0xff, 0xff, 0xfe, 0xff, 0x78, 0x56]); + assert.deepEqual( + [...u8.slice(0, 8)], + [0x34, 0x12, 0xff, 0xff, 0xfe, 0xff, 0x78, 0x56] + ); var b = new ArrayBuffer(64); var u32 = addon.return_uint32array_from_arraybuffer(b); @@ -237,7 +240,10 @@ describe("JsObject", function () { f64[1] = 2.0; f64[2] = 3.141592653589793; assert.deepEqual([...f64.slice(0, 3)], [1.0, 2.0, 3.141592653589793]); - assert.deepEqual([...(new Float64Array(b)).slice(0, 3)], [1.0, 2.0, 3.141592653589793]); + assert.deepEqual( + [...new Float64Array(b).slice(0, 3)], + [1.0, 2.0, 3.141592653589793] + ); var b = new ArrayBuffer(64); var u64 = addon.return_biguint64array_from_arraybuffer(b); @@ -246,8 +252,17 @@ describe("JsObject", function () { u64[0] = 0x1234567887654321n; u64[1] = 0xcafed00d1337c0den; var u8 = new Uint8Array(b); - assert.deepEqual([...u64.slice(0, 2)], [0x1234567887654321n, 0xcafed00d1337c0den]); - assert.deepEqual([...u8.slice(0, 16)], [0x21, 0x43, 0x65, 0x87, 0x78, 0x56, 0x34, 0x12, 0xde, 0xc0, 0x37, 0x13, 0x0d, 0xd0, 0xfe, 0xca]); + assert.deepEqual( + [...u64.slice(0, 2)], + [0x1234567887654321n, 0xcafed00d1337c0den] + ); + assert.deepEqual( + [...u8.slice(0, 16)], + [ + 0x21, 0x43, 0x65, 0x87, 0x78, 0x56, 0x34, 0x12, 0xde, 0xc0, 0x37, 0x13, + 0x0d, 0xd0, 0xfe, 0xca, + ] + ); }); it("gets a new typed array", function () { @@ -255,7 +270,10 @@ describe("JsObject", function () { assert.strictEqual(i32.constructor, Int32Array); assert.strictEqual(i32.byteLength, 64); assert.strictEqual(i32.length, 16); - assert.deepEqual([...i32], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assert.deepEqual( + [...i32], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ); }); it("correctly reads a Buffer using the lock API", function () { From 5b5da7e7a3d00a71ce4efbbef88cf1d7cba6f5d8 Mon Sep 17 00:00:00 2001 From: David Herman Date: Thu, 30 Jun 2022 17:40:23 -0700 Subject: [PATCH 06/31] cargo fmt fixes --- crates/neon/src/prelude.rs | 8 +++---- crates/neon/src/sys/typedarray.rs | 3 +-- crates/neon/src/types_impl/buffer/types.rs | 6 +----- crates/neon/src/types_impl/mod.rs | 6 +++--- crates/neon/src/types_impl/promise.rs | 2 +- test/napi/src/js/objects.rs | 4 +++- test/napi/src/lib.rs | 25 +++++++++++++++++----- 7 files changed, 33 insertions(+), 21 deletions(-) diff --git a/crates/neon/src/prelude.rs b/crates/neon/src/prelude.rs index e92fdcc2e..01b87f55e 100644 --- a/crates/neon/src/prelude.rs +++ b/crates/neon/src/prelude.rs @@ -11,10 +11,10 @@ pub use crate::{ result::{JsResult, NeonResult, ResultExt as NeonResultExt}, types::{ boxed::{Finalize, JsBox}, - JsArray, JsArrayBuffer, JsBigInt64Array, JsBigUint64Array, JsBoolean, JsBuffer, - JsError, JsFloat32Array, JsFloat64Array, JsFunction, JsInt8Array, JsInt16Array, - JsInt32Array, JsNull, JsNumber, JsObject, JsPromise, JsString, JsTypedArray, - JsUint8Array, JsUint16Array, JsUint32Array, JsUndefined, JsValue, Value, + JsArray, JsArrayBuffer, JsBigInt64Array, JsBigUint64Array, JsBoolean, JsBuffer, JsError, + JsFloat32Array, JsFloat64Array, JsFunction, JsInt16Array, JsInt32Array, JsInt8Array, + JsNull, JsNumber, JsObject, JsPromise, JsString, JsTypedArray, JsUint16Array, + JsUint32Array, JsUint8Array, JsUndefined, JsValue, Value, }, }; diff --git a/crates/neon/src/sys/typedarray.rs b/crates/neon/src/sys/typedarray.rs index 30690ef1a..196484406 100644 --- a/crates/neon/src/sys/typedarray.rs +++ b/crates/neon/src/sys/typedarray.rs @@ -46,8 +46,7 @@ pub unsafe fn new( buffer: Local, offset: usize, len: usize, -) -> Result -{ +) -> Result { let mut array = MaybeUninit::uninit(); let status = napi::create_typedarray(env, typ, len, buffer, offset, array.as_mut_ptr()); diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index fbf41b5a4..fd895f497 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -483,7 +483,6 @@ impl JsTypedArray { buffer: Handle, byte_offset: usize, len: usize, - ) -> JsResult<'cx, Self> where C: Context<'cx>, @@ -502,10 +501,7 @@ impl JsTypedArray { } } - pub fn new<'cx, 'a, C>( - cx: &'a mut C, - len: usize, - ) -> JsResult<'cx, Self> + pub fn new<'cx, 'a, C>(cx: &'a mut C, len: usize) -> JsResult<'cx, Self> where C: Context<'cx>, { diff --git a/crates/neon/src/types_impl/mod.rs b/crates/neon/src/types_impl/mod.rs index 97095a490..0a021a765 100644 --- a/crates/neon/src/types_impl/mod.rs +++ b/crates/neon/src/types_impl/mod.rs @@ -37,9 +37,9 @@ use crate::{ pub use self::{ boxed::{Finalize, JsBox}, buffer::types::{ - JsArrayBuffer, JsBuffer, JsBigInt64Array, JsBigUint64Array, JsFloat32Array, - JsFloat64Array, JsInt8Array, JsInt16Array, JsInt32Array, JsTypedArray, - JsUint8Array, JsUint16Array, JsUint32Array, + JsArrayBuffer, JsBigInt64Array, JsBigUint64Array, JsBuffer, JsFloat32Array, JsFloat64Array, + JsInt16Array, JsInt32Array, JsInt8Array, JsTypedArray, JsUint16Array, JsUint32Array, + JsUint8Array, }, error::JsError, promise::{Deferred, JsPromise}, diff --git a/crates/neon/src/types_impl/promise.rs b/crates/neon/src/types_impl/promise.rs index a6ccb4c2b..0aa1d7c00 100644 --- a/crates/neon/src/types_impl/promise.rs +++ b/crates/neon/src/types_impl/promise.rs @@ -2,7 +2,7 @@ use std::ptr; use crate::{ context::{internal::Env, Context}, - handle::{Handle, internal::TransparentNoCopyWrapper, Managed}, + handle::{internal::TransparentNoCopyWrapper, Handle, Managed}, object::Object, result::JsResult, sys::{self, no_panic::FailureBoundary, raw}, diff --git a/test/napi/src/js/objects.rs b/test/napi/src/js/objects.rs index 5b6c5550c..e966b4834 100644 --- a/test/napi/src/js/objects.rs +++ b/test/napi/src/js/objects.rs @@ -191,7 +191,9 @@ pub fn return_float64array_from_arraybuffer(mut cx: FunctionContext) -> JsResult JsFloat64Array::from_array_buffer(&mut cx, buf, 0, len / 8) } -pub fn return_biguint64array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { +pub fn return_biguint64array_from_arraybuffer( + mut cx: FunctionContext, +) -> JsResult { let buf = cx.argument::(0)?; let len = buf.as_slice(&cx).len(); JsBigUint64Array::from_array_buffer(&mut cx, buf, 0, len / 8) diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index b4cf7e11e..6f1ddafd5 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -235,11 +235,26 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { cx.export_function("return_buffer", return_buffer)?; cx.export_function("return_external_buffer", return_external_buffer)?; cx.export_function("return_external_array_buffer", return_external_array_buffer)?; - cx.export_function("return_int8array_from_arraybuffer", return_int8array_from_arraybuffer)?; - cx.export_function("return_int16array_from_arraybuffer", return_int16array_from_arraybuffer)?; - cx.export_function("return_uint32array_from_arraybuffer", return_uint32array_from_arraybuffer)?; - cx.export_function("return_float64array_from_arraybuffer", return_float64array_from_arraybuffer)?; - cx.export_function("return_biguint64array_from_arraybuffer", return_biguint64array_from_arraybuffer)?; + cx.export_function( + "return_int8array_from_arraybuffer", + return_int8array_from_arraybuffer, + )?; + cx.export_function( + "return_int16array_from_arraybuffer", + return_int16array_from_arraybuffer, + )?; + cx.export_function( + "return_uint32array_from_arraybuffer", + return_uint32array_from_arraybuffer, + )?; + cx.export_function( + "return_float64array_from_arraybuffer", + return_float64array_from_arraybuffer, + )?; + cx.export_function( + "return_biguint64array_from_arraybuffer", + return_biguint64array_from_arraybuffer, + )?; cx.export_function("return_new_int32array", return_new_int32array)?; cx.export_function("read_buffer_with_lock", read_buffer_with_lock)?; cx.export_function("read_buffer_with_borrow", read_buffer_with_borrow)?; From bbcd0867201aa114d3a37b34c4a9a585124fefe2 Mon Sep 17 00:00:00 2001 From: David Herman Date: Fri, 1 Jul 2022 10:40:44 -0700 Subject: [PATCH 07/31] Copy nit: avoid the word "number" when describing object types --- crates/neon/src/types_docs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/neon/src/types_docs.rs b/crates/neon/src/types_docs.rs index b58bbfed4..4a0d207f3 100644 --- a/crates/neon/src/types_docs.rs +++ b/crates/neon/src/types_docs.rs @@ -89,7 +89,7 @@ /// /// #### Object Types /// -/// The object type hierarchy further divides into a number of different subtypes: +/// The object type hierarchy further divides into a variety of different subtypes: /// /// ```mermaid /// flowchart LR From 917244473caa18bcfbd574dec2b7626ee43b8231 Mon Sep 17 00:00:00 2001 From: David Herman Date: Fri, 8 Jul 2022 10:49:24 -0700 Subject: [PATCH 08/31] Move `Binary` trait into `crate::types::buffer::private` so it doesn't show up in docs. --- crates/neon/src/types_impl/buffer/mod.rs | 7 +---- crates/neon/src/types_impl/buffer/types.rs | 32 ++++++++++------------ 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/crates/neon/src/types_impl/buffer/mod.rs b/crates/neon/src/types_impl/buffer/mod.rs index 58affa819..a2cd67ab0 100644 --- a/crates/neon/src/types_impl/buffer/mod.rs +++ b/crates/neon/src/types_impl/buffer/mod.rs @@ -14,10 +14,9 @@ use crate::{ }; pub(crate) mod lock; +mod private; pub(super) mod types; -pub use types::Binary; - /// A trait allowing Rust to borrow binary data from the memory buffer of JavaScript /// [typed arrays][typed-arrays]. /// @@ -175,7 +174,3 @@ impl ResultExt for Result { self.or_else(|_| cx.throw_error("BorrowError")) } } - -mod private { - pub trait Sealed {} -} diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index fd895f497..615caf796 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -278,14 +278,6 @@ impl TypedArray for JsArrayBuffer { } } -/// A marker trait for all possible element types of binary buffers. -/// -/// This trait can only be implemented within the Neon library. -pub trait Binary: private::Sealed + Copy { - /// The internal Node-API enum value for this binary type. - const RAW: TypedArrayType; -} - /// The family of JS [typed array][typed-arrays] types. /// /// ## Typed Arrays @@ -379,14 +371,14 @@ pub trait Binary: private::Sealed + Copy { /// [Buffer]: https://nodejs.org/api/buffer.html #[derive(Debug)] #[repr(transparent)] -pub struct JsTypedArray { +pub struct JsTypedArray { local: raw::Local, _type: PhantomData, } -impl private::Sealed for JsTypedArray {} +impl private::Sealed for JsTypedArray {} -unsafe impl TransparentNoCopyWrapper for JsTypedArray { +unsafe impl TransparentNoCopyWrapper for JsTypedArray { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { @@ -394,7 +386,7 @@ unsafe impl TransparentNoCopyWrapper for JsTypedArray { } } -impl Managed for JsTypedArray { +impl Managed for JsTypedArray { fn to_raw(&self) -> raw::Local { self.local } @@ -407,7 +399,7 @@ impl Managed for JsTypedArray { } } -impl TypedArray for JsTypedArray { +impl TypedArray for JsTypedArray { type Item = T; fn as_slice<'cx, 'a, C>(&self, cx: &'a C) -> &'a [Self::Item] @@ -477,7 +469,7 @@ impl TypedArray for JsTypedArray { } } -impl JsTypedArray { +impl JsTypedArray { pub fn from_array_buffer<'cx, 'a, C>( cx: &'a mut C, buffer: Handle, @@ -488,7 +480,13 @@ impl JsTypedArray { C: Context<'cx>, { let result = unsafe { - sys::typedarray::new(cx.env().to_raw(), T::RAW, buffer.to_raw(), byte_offset, len) + sys::typedarray::new( + cx.env().to_raw(), + T::TYPE_TAG, + buffer.to_raw(), + byte_offset, + len, + ) }; if let Ok(arr) = result { @@ -514,8 +512,8 @@ macro_rules! impl_typed_array { ($name:expr, $typ:ty, $($pattern:pat)|+, $tag:ident$(,)?) => { impl private::Sealed for $typ {} - impl Binary for $typ { - const RAW: TypedArrayType = TypedArrayType::$tag; + impl private::Binary for $typ { + const TYPE_TAG: TypedArrayType = TypedArrayType::$tag; } impl Value for JsTypedArray<$typ> {} From 9db216321af56a1372e218c611cea392eb434c2a Mon Sep 17 00:00:00 2001 From: David Herman Date: Fri, 8 Jul 2022 10:53:08 -0700 Subject: [PATCH 09/31] Add missing module. --- crates/neon/src/types_impl/buffer/private.rs | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 crates/neon/src/types_impl/buffer/private.rs diff --git a/crates/neon/src/types_impl/buffer/private.rs b/crates/neon/src/types_impl/buffer/private.rs new file mode 100644 index 000000000..167dc304f --- /dev/null +++ b/crates/neon/src/types_impl/buffer/private.rs @@ -0,0 +1,9 @@ +use crate::sys::TypedArrayType; + +pub trait Sealed {} + +/// A marker trait for all possible element types of binary buffers. +pub trait Binary: Copy { + /// The internal Node-API enum value for this binary type. + const TYPE_TAG: TypedArrayType; +} From ff4e50111d1d70179e7c990f369d2545f974a5b1 Mon Sep 17 00:00:00 2001 From: David Herman Date: Fri, 8 Jul 2022 22:56:44 -0700 Subject: [PATCH 10/31] Move definition of typed array type aliases into the macro. --- crates/neon/Cargo.toml | 1 + crates/neon/src/types_impl/buffer/types.rs | 298 ++++++--------------- 2 files changed, 82 insertions(+), 217 deletions(-) diff --git a/crates/neon/Cargo.toml b/crates/neon/Cargo.toml index dbae50c9b..78b6240f5 100644 --- a/crates/neon/Cargo.toml +++ b/crates/neon/Cargo.toml @@ -26,6 +26,7 @@ smallvec = "1.4.2" once_cell = "1.10.0" neon-macros = { version = "=1.0.0-alpha.1", path = "../neon-macros" } aquamarine = "0.1.11" +doc-comment = "0.3.3" [dependencies.tokio] version = "1.18.2" diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index 615caf796..b039a0e74 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -13,6 +13,8 @@ use crate::{ types::{private::ValueInternal, Value}, }; +use doc_comment::doc_comment; + /// The Node [`Buffer`](https://nodejs.org/api/buffer.html) type. /// /// # Example @@ -509,20 +511,20 @@ impl JsTypedArray { } macro_rules! impl_typed_array { - ($name:expr, $typ:ty, $($pattern:pat)|+, $tag:ident$(,)?) => { - impl private::Sealed for $typ {} + ($typ:ident, $etyp:ty, $($pattern:pat)|+, $tag:ident, $alias:ident, $two:expr$(,)?) => { + impl private::Sealed for $etyp {} - impl private::Binary for $typ { + impl private::Binary for $etyp { const TYPE_TAG: TypedArrayType = TypedArrayType::$tag; } - impl Value for JsTypedArray<$typ> {} + impl Value for JsTypedArray<$etyp> {} - impl Object for JsTypedArray<$typ> {} + impl Object for JsTypedArray<$etyp> {} - impl ValueInternal for JsTypedArray<$typ> { + impl ValueInternal for JsTypedArray<$etyp> { fn name() -> String { - $name.to_string() + stringify!($typ).to_string() } fn is_typeof(env: Env, other: &Other) -> bool { @@ -538,221 +540,83 @@ macro_rules! impl_typed_array { matches!(info.typ, $($pattern)|+) } } + + doc_comment! { + concat!( + "The standard JS [`", + stringify!($typ), + "`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/", + stringify!($typ), + ") type. + +# Example + +``` +# use neon::prelude::*; +use neon::types::buffer::TypedArray; + +fn double(mut cx: FunctionContext) -> JsResult { + let mut array: Handle<", + stringify!($alias), + "> = cx.argument(0)?; + + for elem in array.as_mut_slice(&mut cx).iter_mut() { + *elem *= ", + stringify!($two), + "; + } + + Ok(cx.undefined()) +} +```", + ), + pub type $alias = JsTypedArray<$etyp>; + } }; } -impl_typed_array!("Int8Array", i8, TypedArrayType::I8, I8); +impl_typed_array!(Int8Array, i8, TypedArrayType::I8, I8, JsInt8Array, 2); impl_typed_array!( - "Uint8Array", + Uint8Array, u8, TypedArrayType::U8 | TypedArrayType::U8Clamped, U8, + JsUint8Array, + 2, +); +impl_typed_array!(Int16Array, i16, TypedArrayType::I16, I16, JsInt16Array, 2); +impl_typed_array!(Uint16Array, u16, TypedArrayType::U16, U16, JsUint16Array, 2); +impl_typed_array!(Int32Array, i32, TypedArrayType::I32, I32, JsInt32Array, 2); +impl_typed_array!(Uint32Array, u32, TypedArrayType::U32, U32, JsUint32Array, 2); +impl_typed_array!( + Float32Array, + f32, + TypedArrayType::F32, + F32, + JsFloat32Array, + 2.0, +); +impl_typed_array!( + Float64Array, + f64, + TypedArrayType::F64, + F64, + JsFloat64Array, + 2.0, +); +impl_typed_array!( + BigInt64Array, + i64, + TypedArrayType::I64, + I64, + JsBigInt64Array, + 2, +); +impl_typed_array!( + BigUint64Array, + u64, + TypedArrayType::U64, + U64, + JsBigUint64Array, + 2, ); -impl_typed_array!("Int16Array", i16, TypedArrayType::I16, I16); -impl_typed_array!("Uint16Array", u16, TypedArrayType::U16, U16); -impl_typed_array!("Int32Array", i32, TypedArrayType::I32, I32); -impl_typed_array!("Uint32Array", u32, TypedArrayType::U32, U32); -impl_typed_array!("Float32Array", f32, TypedArrayType::F32, F32); -impl_typed_array!("Float64Array", f64, TypedArrayType::F64, F64); -impl_typed_array!("BigInt64Array", i64, TypedArrayType::I64, I64); -impl_typed_array!("BigUint64Array", u64, TypedArrayType::U64, U64); - -/// The standard JS [`Int8Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int8Array) type. -/// -/// # Example -/// -/// ``` -/// # use neon::prelude::*; -/// use neon::types::buffer::TypedArray; -/// -/// fn double(mut cx: FunctionContext) -> JsResult { -/// let mut array: Handle = cx.argument(0)?; -/// -/// for elem in array.as_mut_slice(&mut cx).iter_mut() { -/// *elem *= 2; -/// } -/// -/// Ok(cx.undefined()) -/// } -/// ``` -pub type JsInt8Array = JsTypedArray; - -/// The standard JS [`Uint8Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) type. -/// -/// # Example -/// -/// ``` -/// # use neon::prelude::*; -/// use neon::types::buffer::TypedArray; -/// -/// fn double(mut cx: FunctionContext) -> JsResult { -/// let mut array: Handle = cx.argument(0)?; -/// -/// for elem in array.as_mut_slice(&mut cx).iter_mut() { -/// *elem *= 2; -/// } -/// -/// Ok(cx.undefined()) -/// } -/// ``` -pub type JsUint8Array = JsTypedArray; - -/// The standard JS [`Int16Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int16Array) type. -/// -/// # Example -/// -/// ``` -/// # use neon::prelude::*; -/// use neon::types::buffer::TypedArray; -/// -/// fn double(mut cx: FunctionContext) -> JsResult { -/// let mut array: Handle = cx.argument(0)?; -/// -/// for elem in array.as_mut_slice(&mut cx).iter_mut() { -/// *elem *= 2; -/// } -/// -/// Ok(cx.undefined()) -/// } -/// ``` -pub type JsInt16Array = JsTypedArray; - -/// The standard JS [`Uint16Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array) type. -/// -/// # Example -/// -/// ``` -/// # use neon::prelude::*; -/// use neon::types::buffer::TypedArray; -/// -/// fn double(mut cx: FunctionContext) -> JsResult { -/// let mut array: Handle = cx.argument(0)?; -/// -/// for elem in array.as_mut_slice(&mut cx).iter_mut() { -/// *elem *= 2; -/// } -/// -/// Ok(cx.undefined()) -/// } -/// ``` -pub type JsUint16Array = JsTypedArray; - -/// The standard JS [`Int32Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int32Array) type. -/// -/// # Example -/// -/// ``` -/// # use neon::prelude::*; -/// use neon::types::buffer::TypedArray; -/// -/// fn double(mut cx: FunctionContext) -> JsResult { -/// let mut array: Handle = cx.argument(0)?; -/// -/// for elem in array.as_mut_slice(&mut cx).iter_mut() { -/// *elem *= 2; -/// } -/// -/// Ok(cx.undefined()) -/// } -/// ``` -pub type JsInt32Array = JsTypedArray; - -/// The standard JS [`Uint32Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array) type. -/// -/// # Example -/// -/// ``` -/// # use neon::prelude::*; -/// use neon::types::buffer::TypedArray; -/// -/// fn double(mut cx: FunctionContext) -> JsResult { -/// let mut array: Handle = cx.argument(0)?; -/// -/// for elem in array.as_mut_slice(&mut cx).iter_mut() { -/// *elem *= 2; -/// } -/// -/// Ok(cx.undefined()) -/// } -/// ``` -pub type JsUint32Array = JsTypedArray; - -/// The standard JS [`BigInt64Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array) type. -/// -/// # Example -/// -/// ``` -/// # use neon::prelude::*; -/// use neon::types::buffer::TypedArray; -/// -/// fn double(mut cx: FunctionContext) -> JsResult { -/// let mut array: Handle = cx.argument(0)?; -/// -/// for elem in array.as_mut_slice(&mut cx).iter_mut() { -/// *elem *= 2; -/// } -/// -/// Ok(cx.undefined()) -/// } -/// ``` -pub type JsBigInt64Array = JsTypedArray; - -/// The standard JS [`BigUint64Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/BigUint64Array) type. -/// -/// # Example -/// -/// ``` -/// # use neon::prelude::*; -/// use neon::types::buffer::TypedArray; -/// -/// fn double(mut cx: FunctionContext) -> JsResult { -/// let mut array: Handle = cx.argument(0)?; -/// -/// for elem in array.as_mut_slice(&mut cx).iter_mut() { -/// *elem *= 2; -/// } -/// -/// Ok(cx.undefined()) -/// } -/// ``` -pub type JsBigUint64Array = JsTypedArray; - -/// The standard JS [`Float32Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Float32Array) type. -/// -/// # Example -/// -/// ``` -/// # use neon::prelude::*; -/// use neon::types::buffer::TypedArray; -/// -/// fn double(mut cx: FunctionContext) -> JsResult { -/// let mut array: Handle = cx.argument(0)?; -/// -/// for elem in array.as_mut_slice(&mut cx).iter_mut() { -/// *elem *= 2.0; -/// } -/// -/// Ok(cx.undefined()) -/// } -/// ``` -pub type JsFloat32Array = JsTypedArray; - -/// The standard JS [`Float64Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Float64Array) type. -/// -/// # Example -/// -/// ``` -/// # use neon::prelude::*; -/// use neon::types::buffer::TypedArray; -/// -/// fn double(mut cx: FunctionContext) -> JsResult { -/// let mut array: Handle = cx.argument(0)?; -/// -/// for elem in array.as_mut_slice(&mut cx).iter_mut() { -/// *elem *= 2.0; -/// } -/// -/// Ok(cx.undefined()) -/// } -/// ``` -pub type JsFloat64Array = JsTypedArray; From 1a49e5ba1e7ee0404d88f81e19e258e5aadda220 Mon Sep 17 00:00:00 2001 From: David Herman Date: Fri, 8 Jul 2022 23:27:47 -0700 Subject: [PATCH 11/31] Elide lifetimes --- crates/neon/src/types_impl/buffer/types.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index b039a0e74..8fb3d899e 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -472,8 +472,8 @@ impl TypedArray for JsTypedArray { } impl JsTypedArray { - pub fn from_array_buffer<'cx, 'a, C>( - cx: &'a mut C, + pub fn from_array_buffer<'cx, C>( + cx: &mut C, buffer: Handle, byte_offset: usize, len: usize, @@ -501,7 +501,7 @@ impl JsTypedArray { } } - pub fn new<'cx, 'a, C>(cx: &'a mut C, len: usize) -> JsResult<'cx, Self> + pub fn new<'cx, C>(cx: &mut C, len: usize) -> JsResult<'cx, Self> where C: Context<'cx>, { From 84c5fd8786c8553ad79913a8a4634db9bc7372c2 Mon Sep 17 00:00:00 2001 From: David Herman Date: Sun, 10 Jul 2022 22:50:31 -0700 Subject: [PATCH 12/31] - Change `from_array_buffer()` to `from_buffer_region()` and add more convenient `from_buffer()` - Add `TypedArray::byte_length()` - Add `JsTypedArray::{byte_offset, buffer}` - Revert visibility of Binary back to public to enable typed array abstractions - Add tests for buffer aliasing and argument validation - Add tests for `byte_length`, `byte_offset`, and `buffer` methods --- crates/neon/src/sys/arraybuffer.rs | 14 +++ crates/neon/src/sys/buffer.rs | 14 +++ crates/neon/src/types_impl/buffer/mod.rs | 12 +- crates/neon/src/types_impl/buffer/private.rs | 9 -- crates/neon/src/types_impl/buffer/types.rs | 95 ++++++++++++++-- test/napi/lib/objects.js | 112 +++++++++++++++++++ test/napi/src/js/objects.rs | 84 ++++++++++++-- test/napi/src/lib.rs | 5 + 8 files changed, 315 insertions(+), 30 deletions(-) delete mode 100644 crates/neon/src/types_impl/buffer/private.rs diff --git a/crates/neon/src/sys/arraybuffer.rs b/crates/neon/src/sys/arraybuffer.rs index 98fb4a147..6c911af42 100644 --- a/crates/neon/src/sys/arraybuffer.rs +++ b/crates/neon/src/sys/arraybuffer.rs @@ -65,3 +65,17 @@ pub unsafe fn as_mut_slice<'a>(env: Env, buf: Local) -> &'a mut [u8] { slice::from_raw_parts_mut(data.assume_init().cast(), size) } + +/// # Safety +/// * Caller must ensure `env` and `buf` are valid +pub unsafe fn size(env: Env, buf: Local) -> usize { + let mut data = MaybeUninit::uninit(); + let mut size = 0usize; + + assert_eq!( + napi::get_arraybuffer_info(env, buf, data.as_mut_ptr(), &mut size as *mut _), + napi::Status::Ok, + ); + + size +} diff --git a/crates/neon/src/sys/buffer.rs b/crates/neon/src/sys/buffer.rs index a991c4a00..7bd43fff5 100644 --- a/crates/neon/src/sys/buffer.rs +++ b/crates/neon/src/sys/buffer.rs @@ -74,3 +74,17 @@ pub unsafe fn as_mut_slice<'a>(env: Env, buf: Local) -> &'a mut [u8] { slice::from_raw_parts_mut(data.assume_init().cast(), size) } + +/// # Safety +/// * Caller must ensure `env` and `buf` are valid +pub unsafe fn size(env: Env, buf: Local) -> usize { + let mut data = MaybeUninit::uninit(); + let mut size = 0usize; + + assert_eq!( + napi::get_buffer_info(env, buf, data.as_mut_ptr(), &mut size as *mut _), + napi::Status::Ok, + ); + + size +} diff --git a/crates/neon/src/types_impl/buffer/mod.rs b/crates/neon/src/types_impl/buffer/mod.rs index a2cd67ab0..805134b0a 100644 --- a/crates/neon/src/types_impl/buffer/mod.rs +++ b/crates/neon/src/types_impl/buffer/mod.rs @@ -14,9 +14,10 @@ use crate::{ }; pub(crate) mod lock; -mod private; pub(super) mod types; +pub use types::Binary; + /// A trait allowing Rust to borrow binary data from the memory buffer of JavaScript /// [typed arrays][typed-arrays]. /// @@ -82,6 +83,11 @@ pub trait TypedArray: private::Sealed { ) -> Result, BorrowError> where C: Context<'cx>; + + /// Returns the size, in bytes, of the allocated binary data. + fn byte_length<'cx, C>(&self, cx: &mut C) -> usize + where + C: Context<'cx>; } #[derive(Debug)] @@ -174,3 +180,7 @@ impl ResultExt for Result { self.or_else(|_| cx.throw_error("BorrowError")) } } + +mod private { + pub trait Sealed {} +} diff --git a/crates/neon/src/types_impl/buffer/private.rs b/crates/neon/src/types_impl/buffer/private.rs deleted file mode 100644 index 167dc304f..000000000 --- a/crates/neon/src/types_impl/buffer/private.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::sys::TypedArrayType; - -pub trait Sealed {} - -/// A marker trait for all possible element types of binary buffers. -pub trait Binary: Copy { - /// The internal Node-API enum value for this binary type. - const TYPE_TAG: TypedArrayType; -} diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index 8fb3d899e..53354b80e 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -155,6 +155,10 @@ impl TypedArray for JsBuffer { sys::buffer::as_mut_slice(lock.cx.env().to_raw(), self.to_raw()) }) } + + fn byte_length<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { + unsafe { sys::buffer::size(cx.env().to_raw(), self.to_raw()) } + } } /// The standard JS [`ArrayBuffer`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) type. @@ -278,6 +282,18 @@ impl TypedArray for JsArrayBuffer { sys::arraybuffer::as_mut_slice(lock.cx.env().to_raw(), self.to_raw()) }) } + + fn byte_length<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { + unsafe { sys::arraybuffer::size(cx.env().to_raw(), self.to_raw()) } + } +} + +/// A marker trait for all possible element types of binary buffers. +/// +/// This trait can only be implemented within the Neon library. +pub trait Binary: private::Sealed + Copy { + /// The internal Node-API enum value for this binary type. + const TYPE_TAG: TypedArrayType; } /// The family of JS [typed array][typed-arrays] types. @@ -373,14 +389,14 @@ impl TypedArray for JsArrayBuffer { /// [Buffer]: https://nodejs.org/api/buffer.html #[derive(Debug)] #[repr(transparent)] -pub struct JsTypedArray { +pub struct JsTypedArray { local: raw::Local, _type: PhantomData, } -impl private::Sealed for JsTypedArray {} +impl private::Sealed for JsTypedArray {} -unsafe impl TransparentNoCopyWrapper for JsTypedArray { +unsafe impl TransparentNoCopyWrapper for JsTypedArray { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { @@ -388,7 +404,7 @@ unsafe impl TransparentNoCopyWrapper for JsTypedArray { } } -impl Managed for JsTypedArray { +impl Managed for JsTypedArray { fn to_raw(&self) -> raw::Local { self.local } @@ -401,7 +417,7 @@ impl Managed for JsTypedArray { } } -impl TypedArray for JsTypedArray { +impl TypedArray for JsTypedArray { type Item = T; fn as_slice<'cx, 'a, C>(&self, cx: &'a C) -> &'a [Self::Item] @@ -469,10 +485,37 @@ impl TypedArray for JsTypedArray { ) } } + + fn byte_length<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { + unsafe { + let env = cx.env().to_raw(); + let value = self.to_raw(); + let info = sys::typedarray::info(env, value); + info.length * std::mem::size_of::() + } + } } -impl JsTypedArray { - pub fn from_array_buffer<'cx, C>( +impl JsTypedArray { + pub fn from_buffer<'cx, C>(cx: &mut C, buffer: Handle) -> JsResult<'cx, Self> + where + C: Context<'cx>, + { + let byte_length = buffer.byte_length(cx); + let elt_size = std::mem::size_of::(); + let len = byte_length / elt_size; + + if (len * elt_size) != byte_length { + panic!( + "byte length of typed array should be a multiple of {}", + elt_size + ); + } + + Self::from_buffer_region(cx, buffer, 0, len) + } + + pub fn from_buffer_region<'cx, C>( cx: &mut C, buffer: Handle, byte_offset: usize, @@ -506,7 +549,41 @@ impl JsTypedArray { C: Context<'cx>, { let buffer = cx.array_buffer(len * std::mem::size_of::())?; - Self::from_array_buffer(cx, buffer, 0, len) + Self::from_buffer_region(cx, buffer, 0, len) + } + + pub fn buffer<'cx, C>(&self, cx: &mut C) -> Handle<'cx, JsArrayBuffer> + where + C: Context<'cx>, + { + let env = cx.env(); + let info = unsafe { sys::typedarray::info(env.to_raw(), self.to_raw()) }; + Handle::new_internal(JsArrayBuffer::from_raw(env, info.buf)) + } + + pub fn byte_offset<'cx, C>(&self, cx: &mut C) -> usize + where + C: Context<'cx>, + { + let env = cx.env(); + let info = unsafe { sys::typedarray::info(env.to_raw(), self.to_raw()) }; + info.offset + } + + pub fn len<'cx, C>(&self, cx: &mut C) -> usize + where + C: Context<'cx>, + { + let env = cx.env(); + let info = unsafe { sys::typedarray::info(env.to_raw(), self.to_raw()) }; + info.length + } + + pub fn byte_length<'cx, C>(&self, cx: &mut C) -> usize + where + C: Context<'cx>, + { + self.len(cx) * std::mem::size_of::() } } @@ -514,7 +591,7 @@ macro_rules! impl_typed_array { ($typ:ident, $etyp:ty, $($pattern:pat)|+, $tag:ident, $alias:ident, $two:expr$(,)?) => { impl private::Sealed for $etyp {} - impl private::Binary for $etyp { + impl Binary for $etyp { const TYPE_TAG: TypedArrayType = TypedArrayType::$tag; } diff --git a/test/napi/lib/objects.js b/test/napi/lib/objects.js index 1132b50a0..3512fae71 100644 --- a/test/napi/lib/objects.js +++ b/test/napi/lib/objects.js @@ -276,6 +276,118 @@ describe("JsObject", function () { ); }); + it("gets correct typed array info", function () { + var buf = new ArrayBuffer(128); + + var a = addon.return_int8array_from_arraybuffer(buf); + var info = addon.get_typed_array_info(a); + + assert.strictEqual(buf, a.buffer); + assert.strictEqual(0, a.byteOffset); + assert.strictEqual(128, a.length); + assert.strictEqual(128, a.byteLength); + + assert.strictEqual(buf, info.buffer); + assert.strictEqual(0, info.byteOffset); + assert.strictEqual(128, info.length); + assert.strictEqual(128, info.byteLength); + + var a = addon.return_int16array_from_arraybuffer(buf); + var info = addon.get_typed_array_info(a); + + assert.strictEqual(buf, a.buffer); + assert.strictEqual(0, a.byteOffset); + assert.strictEqual(64, a.length); + assert.strictEqual(128, a.byteLength); + + assert.strictEqual(buf, info.buffer); + assert.strictEqual(0, info.byteOffset); + assert.strictEqual(64, info.length); + assert.strictEqual(128, info.byteLength); + + var a = addon.return_uint32array_from_arraybuffer(buf); + var info = addon.get_typed_array_info(a); + + assert.strictEqual(buf, a.buffer); + assert.strictEqual(0, a.byteOffset); + assert.strictEqual(32, a.length); + assert.strictEqual(128, a.byteLength); + + assert.strictEqual(buf, info.buffer); + assert.strictEqual(0, info.byteOffset); + assert.strictEqual(32, info.length); + assert.strictEqual(128, info.byteLength); + + var a = addon.return_biguint64array_from_arraybuffer(buf); + var info = addon.get_typed_array_info(a); + + assert.strictEqual(buf, a.buffer); + assert.strictEqual(0, a.byteOffset); + assert.strictEqual(16, a.length); + assert.strictEqual(128, a.byteLength); + + assert.strictEqual(buf, info.buffer); + assert.strictEqual(0, info.byteOffset); + assert.strictEqual(16, info.length); + assert.strictEqual(128, info.byteLength); + }); + + it("correctly constructs a view over a slice of a buffer", function () { + var buf = new ArrayBuffer(128); + + var a = addon.return_uint32array_from_arraybuffer_region(buf, 16, 4); + var info = addon.get_typed_array_info(a); + + assert.strictEqual(buf, a.buffer); + assert.strictEqual(16, a.byteOffset); + assert.strictEqual(4, a.length); + assert.strictEqual(16, a.byteLength); + + assert.strictEqual(buf, info.buffer); + assert.strictEqual(16, info.byteOffset); + assert.strictEqual(4, info.length); + assert.strictEqual(16, info.byteLength); + + a[0] = 17; + a[1] = 42; + a[2] = 100; + a[3] = 1000; + + var left = buf.slice(0, 16); + var middle = buf.slice(16, 32); + var right = buf.slice(32); + + assert.deepEqual(new Uint8Array(16), new Uint8Array(left)); + assert.deepEqual( + new Uint8Array([17, 0, 0, 0, 42, 0, 0, 0, 100, 0, 0, 0, 232, 3, 0, 0]), + new Uint8Array(middle) + ); + assert.deepEqual(new Uint8Array(96), new Uint8Array(right)); + }); + + it("properly fails to construct typed arrays with invalid arguments", function () { + var buf = new ArrayBuffer(32); + try { + addon.return_uint32array_from_arraybuffer_region(buf, 1, 4); + assert.fail("should have thrown for unaligned offset"); + } catch (expected) {} + + try { + addon.return_uint32array_from_arraybuffer_region(buf, 100, 104); + assert.fail("should have thrown for bounds check failure"); + } catch (expected) {} + + try { + addon.return_uint32array_from_arraybuffer_region(buf, 0, 5); + assert.fail("should have thrown for invalid length"); + } catch (expected) {} + + try { + addon.return_uint32array_from_arraybuffer_region(buf, 0, 10); + assert.fail("should have thrown for excessive length"); + } catch (expected) {} + }); + it("correctly reads a Buffer using the lock API", function () { var b = Buffer.allocUnsafe(16); b.writeUInt8(147, 0); diff --git a/test/napi/src/js/objects.rs b/test/napi/src/js/objects.rs index e966b4834..c9624adcf 100644 --- a/test/napi/src/js/objects.rs +++ b/test/napi/src/js/objects.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use neon::{ prelude::*, - types::buffer::{BorrowError, TypedArray}, + types::buffer::{Binary, BorrowError, TypedArray}, }; pub fn return_js_global_object(mut cx: FunctionContext) -> JsResult { @@ -169,34 +169,29 @@ pub fn return_external_array_buffer(mut cx: FunctionContext) -> JsResult JsResult { let buf = cx.argument::(0)?; - let len = buf.as_slice(&cx).len(); - JsInt8Array::from_array_buffer(&mut cx, buf, 0, len) + JsInt8Array::from_buffer(&mut cx, buf) } pub fn return_int16array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { let buf = cx.argument::(0)?; - let len = buf.as_slice(&cx).len(); - JsInt16Array::from_array_buffer(&mut cx, buf, 0, len / 2) + JsInt16Array::from_buffer(&mut cx, buf) } pub fn return_uint32array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { let buf = cx.argument::(0)?; - let len = buf.as_slice(&cx).len(); - JsUint32Array::from_array_buffer(&mut cx, buf, 0, len / 4) + JsUint32Array::from_buffer(&mut cx, buf) } pub fn return_float64array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { let buf = cx.argument::(0)?; - let len = buf.as_slice(&cx).len(); - JsFloat64Array::from_array_buffer(&mut cx, buf, 0, len / 8) + JsFloat64Array::from_buffer(&mut cx, buf) } pub fn return_biguint64array_from_arraybuffer( mut cx: FunctionContext, ) -> JsResult { let buf = cx.argument::(0)?; - let len = buf.as_slice(&cx).len(); - JsBigUint64Array::from_array_buffer(&mut cx, buf, 0, len / 8) + JsBigUint64Array::from_buffer(&mut cx, buf) } pub fn return_new_int32array(mut cx: FunctionContext) -> JsResult { @@ -204,6 +199,73 @@ pub fn return_new_int32array(mut cx: FunctionContext) -> JsResult JsInt32Array::new(&mut cx, len) } +pub fn return_uint32array_from_arraybuffer_region( + mut cx: FunctionContext, +) -> JsResult { + let buf = cx.argument::(0)?; + let byte_offset = cx.argument::(1)?; + let byte_offset = byte_offset.value(&mut cx); + let len = cx.argument::(2)?; + let len = len.value(&mut cx); + JsUint32Array::from_buffer_region(&mut cx, buf, byte_offset as usize, len as usize) +} + +fn typed_array_info<'cx, C, T: Binary>( + cx: &mut C, + a: Handle<'cx, JsTypedArray>, +) -> JsResult<'cx, JsObject> +where + C: Context<'cx>, +{ + let byte_offset = a.byte_offset(cx); + let byte_offset = cx.number(byte_offset as u32); + + let len = a.len(cx); + let len = cx.number(len as u32); + + let byte_length = a.byte_length(cx); + let byte_length = cx.number(byte_length as u32); + + let buffer = a.buffer(cx); + + let obj = cx.empty_object(); + + obj.set(cx, "byteOffset", byte_offset)?; + obj.set(cx, "length", len)?; + obj.set(cx, "byteLength", byte_length)?; + obj.set(cx, "buffer", buffer)?; + + Ok(obj) +} + +pub fn get_typed_array_info(mut cx: FunctionContext) -> JsResult { + let x = cx.argument::(0)?; + + if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else { + cx.throw_type_error("expected a typed array") + } +} + pub fn read_buffer_with_lock(mut cx: FunctionContext) -> JsResult { let b: Handle = cx.argument(0)?; let i = cx.argument::(1)?.value(&mut cx) as usize; diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index 6f1ddafd5..99ed8a44d 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -256,6 +256,11 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { return_biguint64array_from_arraybuffer, )?; cx.export_function("return_new_int32array", return_new_int32array)?; + cx.export_function( + "return_uint32array_from_arraybuffer_region", + return_uint32array_from_arraybuffer_region, + )?; + cx.export_function("get_typed_array_info", get_typed_array_info)?; cx.export_function("read_buffer_with_lock", read_buffer_with_lock)?; cx.export_function("read_buffer_with_borrow", read_buffer_with_borrow)?; cx.export_function("write_buffer_with_lock", write_buffer_with_lock)?; From bbf3cda2d40a610d71220b3eee19e51e08a8d932 Mon Sep 17 00:00:00 2001 From: David Herman Date: Sun, 10 Jul 2022 23:38:49 -0700 Subject: [PATCH 13/31] Add API docs for typed array methods --- crates/neon/src/types_impl/buffer/types.rs | 36 +++++++++++++++++----- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index 53354b80e..f691b542a 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -497,6 +497,9 @@ impl TypedArray for JsTypedArray { } impl JsTypedArray { + /// Constructs a typed array that views `buffer`. + /// + /// The resulting typed array has `(buffer.byte_length() / size_of::())` elements. pub fn from_buffer<'cx, C>(cx: &mut C, buffer: Handle) -> JsResult<'cx, Self> where C: Context<'cx>, @@ -515,6 +518,11 @@ impl JsTypedArray { Self::from_buffer_region(cx, buffer, 0, len) } + /// Constructs a typed array that views a region of `buffer` starting + /// at the specified byte offset and with the specified number of elements. + /// + /// The resulting typed array has `len` elements and byte length + /// `(len * size_of::())`. pub fn from_buffer_region<'cx, C>( cx: &mut C, buffer: Handle, @@ -544,6 +552,10 @@ impl JsTypedArray { } } + /// Constructs a new typed array of length `len`. + /// + /// The resulting typed array has a newly allocated storage buffer of + /// size `(len * size_of::())` bytes. pub fn new<'cx, C>(cx: &mut C, len: usize) -> JsResult<'cx, Self> where C: Context<'cx>, @@ -552,6 +564,13 @@ impl JsTypedArray { Self::from_buffer_region(cx, buffer, 0, len) } + /// Returns the [`JsArrayBuffer`](JsArrayBuffer) that owns the underlying storage buffer + /// for this typed array. + /// + /// Note that the typed array may only reference a region of the buffer; use the + /// [`byte_offset()`](JsTypedArray::byte_offset) and + /// [`byte_length()`](crate::types::buffer::TypedArray::byte_length) methods to + /// determine the region. pub fn buffer<'cx, C>(&self, cx: &mut C) -> Handle<'cx, JsArrayBuffer> where C: Context<'cx>, @@ -561,6 +580,8 @@ impl JsTypedArray { Handle::new_internal(JsArrayBuffer::from_raw(env, info.buf)) } + /// Returns the offset (in bytes) of the typed array from the start of its + /// [`JsArrayBuffer`](JsArrayBuffer). pub fn byte_offset<'cx, C>(&self, cx: &mut C) -> usize where C: Context<'cx>, @@ -570,6 +591,14 @@ impl JsTypedArray { info.offset } + /// Returns the length of the typed array, i.e. the number of elements. + /// + /// Note that, depending on the element size, this is not necessarily the same as + /// [`byte_length()`](crate::types::buffer::TypedArray::byte_length). In particular: + /// + /// ```ignore + /// self.byte_length() == self.len() * size_of::() + /// ``` pub fn len<'cx, C>(&self, cx: &mut C) -> usize where C: Context<'cx>, @@ -578,13 +607,6 @@ impl JsTypedArray { let info = unsafe { sys::typedarray::info(env.to_raw(), self.to_raw()) }; info.length } - - pub fn byte_length<'cx, C>(&self, cx: &mut C) -> usize - where - C: Context<'cx>, - { - self.len(cx) * std::mem::size_of::() - } } macro_rules! impl_typed_array { From 45780d5a96a08e3e8718faf372984ea16328c48e Mon Sep 17 00:00:00 2001 From: David Herman Date: Mon, 11 Jul 2022 22:23:06 -0700 Subject: [PATCH 14/31] Make `JsTypedArray` into a wide pointer (like `JsBox`) that caches all the metadata, avoiding extra FFI calls to retrieve that data. --- crates/neon/src/types_impl/buffer/mod.rs | 13 ++++++ crates/neon/src/types_impl/buffer/types.rs | 54 ++++++++++++---------- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/crates/neon/src/types_impl/buffer/mod.rs b/crates/neon/src/types_impl/buffer/mod.rs index 805134b0a..eea40626c 100644 --- a/crates/neon/src/types_impl/buffer/mod.rs +++ b/crates/neon/src/types_impl/buffer/mod.rs @@ -182,5 +182,18 @@ impl ResultExt for Result { } mod private { + use super::Binary; + use crate::sys::raw; + use std::marker::PhantomData; + pub trait Sealed {} + + #[derive(Debug, Clone)] + pub struct JsTypedArrayInner { + pub(super) local: raw::Local, + pub(super) buffer: raw::Local, + pub(super) byte_offset: usize, + pub(super) len: usize, + pub(super) _type: PhantomData, + } } diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index f691b542a..f2393e7ac 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -8,7 +8,8 @@ use crate::{ sys::{self, raw, TypedArrayType}, types::buffer::{ lock::{Ledger, Lock}, - private, BorrowError, Ref, RefMut, TypedArray, + private::{self, JsTypedArrayInner}, + BorrowError, Ref, RefMut, TypedArray, }, types::{private::ValueInternal, Value}, }; @@ -291,11 +292,13 @@ impl TypedArray for JsArrayBuffer { /// A marker trait for all possible element types of binary buffers. /// /// This trait can only be implemented within the Neon library. -pub trait Binary: private::Sealed + Copy { +pub trait Binary: private::Sealed + Copy + std::fmt::Debug { /// The internal Node-API enum value for this binary type. const TYPE_TAG: TypedArrayType; } +impl Copy for JsTypedArrayInner {} + /// The family of JS [typed array][typed-arrays] types. /// /// ## Typed Arrays @@ -389,31 +392,35 @@ pub trait Binary: private::Sealed + Copy { /// [Buffer]: https://nodejs.org/api/buffer.html #[derive(Debug)] #[repr(transparent)] -pub struct JsTypedArray { - local: raw::Local, - _type: PhantomData, -} +pub struct JsTypedArray(JsTypedArrayInner); impl private::Sealed for JsTypedArray {} unsafe impl TransparentNoCopyWrapper for JsTypedArray { - type Inner = raw::Local; + type Inner = JsTypedArrayInner; fn into_inner(self) -> Self::Inner { - self.local + self.0 } } impl Managed for JsTypedArray { fn to_raw(&self) -> raw::Local { - self.local + self.0.local } - fn from_raw(_env: Env, local: raw::Local) -> Self { - Self { + fn from_raw(env: Env, local: raw::Local) -> Self { + // Safety: Recomputing this information ensures that the lifetime of the + // buffer handle matches the lifetime of the typed array handle. + let info = unsafe { sys::typedarray::info(env.to_raw(), local) }; + + Self(JsTypedArrayInner { local, + buffer: info.buf, + byte_offset: info.offset, + len: info.length, _type: PhantomData, - } + }) } } @@ -543,10 +550,13 @@ impl JsTypedArray { }; if let Ok(arr) = result { - Ok(Handle::new_internal(Self { + Ok(Handle::new_internal(Self(JsTypedArrayInner { local: arr, + buffer: buffer.to_raw(), + byte_offset, + len, _type: PhantomData, - })) + }))) } else { Err(Throw::new()) } @@ -575,20 +585,16 @@ impl JsTypedArray { where C: Context<'cx>, { - let env = cx.env(); - let info = unsafe { sys::typedarray::info(env.to_raw(), self.to_raw()) }; - Handle::new_internal(JsArrayBuffer::from_raw(env, info.buf)) + Handle::new_internal(JsArrayBuffer::from_raw(cx.env(), self.0.buffer)) } /// Returns the offset (in bytes) of the typed array from the start of its /// [`JsArrayBuffer`](JsArrayBuffer). - pub fn byte_offset<'cx, C>(&self, cx: &mut C) -> usize + pub fn byte_offset<'cx, C>(&self, _cx: &mut C) -> usize where C: Context<'cx>, { - let env = cx.env(); - let info = unsafe { sys::typedarray::info(env.to_raw(), self.to_raw()) }; - info.offset + self.0.byte_offset } /// Returns the length of the typed array, i.e. the number of elements. @@ -599,13 +605,11 @@ impl JsTypedArray { /// ```ignore /// self.byte_length() == self.len() * size_of::() /// ``` - pub fn len<'cx, C>(&self, cx: &mut C) -> usize + pub fn len<'cx, C>(&self, _cx: &mut C) -> usize where C: Context<'cx>, { - let env = cx.env(); - let info = unsafe { sys::typedarray::info(env.to_raw(), self.to_raw()) }; - info.length + self.0.len } } From e119b79531cc46dcf6a418a72f6993af64699d53 Mon Sep 17 00:00:00 2001 From: David Herman Date: Tue, 12 Jul 2022 00:02:19 -0700 Subject: [PATCH 15/31] Docs copy edit --- crates/neon/src/types_impl/buffer/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index f2393e7ac..844c0aafb 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -577,7 +577,7 @@ impl JsTypedArray { /// Returns the [`JsArrayBuffer`](JsArrayBuffer) that owns the underlying storage buffer /// for this typed array. /// - /// Note that the typed array may only reference a region of the buffer; use the + /// Note that the typed array might only reference a region of the buffer; use the /// [`byte_offset()`](JsTypedArray::byte_offset) and /// [`byte_length()`](crate::types::buffer::TypedArray::byte_length) methods to /// determine the region. From 03c0cf987dc14fb7ced36406a1514ee33a0cf718 Mon Sep 17 00:00:00 2001 From: David Herman Date: Tue, 12 Jul 2022 00:05:13 -0700 Subject: [PATCH 16/31] `TypedArray::byte_length()` doesn't need an FFI call for `JsTypedArray` --- crates/neon/src/types_impl/buffer/types.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index 844c0aafb..638c7ffca 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -494,12 +494,7 @@ impl TypedArray for JsTypedArray { } fn byte_length<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { - unsafe { - let env = cx.env().to_raw(); - let value = self.to_raw(); - let info = sys::typedarray::info(env, value); - info.length * std::mem::size_of::() - } + self.len(cx) * std::mem::size_of::() } } From 776ba84d0345d3a0812e9361729ad066660678f0 Mon Sep 17 00:00:00 2001 From: David Herman Date: Thu, 14 Jul 2022 22:05:15 -0700 Subject: [PATCH 17/31] Address all Clippy warnings and errors. --- crates/neon/src/context/mod.rs | 4 ++-- crates/neon/src/handle/mod.rs | 2 +- crates/neon/src/lifecycle.rs | 8 +++----- crates/neon/src/sys/no_panic.rs | 8 +++----- crates/neon/src/sys/tsfn.rs | 2 +- crates/neon/src/types_impl/buffer/mod.rs | 8 ++++---- crates/neon/src/types_impl/buffer/types.rs | 4 ++++ crates/neon/src/types_impl/error.rs | 2 +- crates/neon/src/types_impl/mod.rs | 1 + crates/neon/src/types_impl/promise.rs | 2 +- test/napi/src/js/futures.rs | 4 ++-- test/napi/src/lib.rs | 15 ++++++--------- 12 files changed, 29 insertions(+), 31 deletions(-) diff --git a/crates/neon/src/context/mod.rs b/crates/neon/src/context/mod.rs index c8f325a8c..e51953f47 100644 --- a/crates/neon/src/context/mod.rs +++ b/crates/neon/src/context/mod.rs @@ -322,12 +322,12 @@ pub trait Context<'a>: ContextInternal<'a> { /// Convenience method for creating a `JsNull` value. fn null(&mut self) -> Handle<'a, JsNull> { - return JsNull::new(self); + JsNull::new(self) } /// Convenience method for creating a `JsUndefined` value. fn undefined(&mut self) -> Handle<'a, JsUndefined> { - return JsUndefined::new(self); + JsUndefined::new(self) } /// Convenience method for creating an empty `JsObject` value. diff --git a/crates/neon/src/handle/mod.rs b/crates/neon/src/handle/mod.rs index 0d61fded0..57f6aaa67 100644 --- a/crates/neon/src/handle/mod.rs +++ b/crates/neon/src/handle/mod.rs @@ -88,7 +88,7 @@ pub struct Handle<'a, T: Managed + 'a> { impl<'a, T: Managed> Clone for Handle<'a, T> { fn clone(&self) -> Self { Self { - value: self.value.clone(), + value: self.value, phantom: PhantomData, } } diff --git a/crates/neon/src/lifecycle.rs b/crates/neon/src/lifecycle.rs index ae2fc877b..f81c6ea86 100644 --- a/crates/neon/src/lifecycle.rs +++ b/crates/neon/src/lifecycle.rs @@ -131,7 +131,7 @@ impl LocalCell { // Kick off a new transaction and drop it before getting the result. { let mut tx = TryInitTransaction::new(cx, id); - tx.run(|cx| Ok(f(cx)?))?; + tx.run(|cx| f(cx))?; } // If we're here, the transaction has succeeded, so get the result. @@ -195,11 +195,9 @@ impl<'cx, 'a, C: Context<'cx>> TryInitTransaction<'cx, 'a, C> { InstanceData::locals(self.cx).get(self.id) } + #[allow(clippy::wrong_self_convention)] fn is_trying(&mut self) -> bool { - match self.cell() { - LocalCell::Trying => true, - _ => false, - } + matches!(self.cell(), LocalCell::Trying) } } diff --git a/crates/neon/src/sys/no_panic.rs b/crates/neon/src/sys/no_panic.rs index 02fcb66bc..682d66074 100644 --- a/crates/neon/src/sys/no_panic.rs +++ b/crates/neon/src/sys/no_panic.rs @@ -210,13 +210,11 @@ unsafe fn error_from_message(env: Env, msg: &str) -> Local { let status = napi::create_error(env, ptr::null_mut(), msg, err.as_mut_ptr()); - let err = if status == napi::Status::Ok { + if status == napi::Status::Ok { err.assume_init() } else { fatal_error("Failed to create an Error"); - }; - - err + } } #[track_caller] @@ -246,7 +244,7 @@ unsafe fn panic_msg(panic: &Panic) -> Option<&str> { if let Some(msg) = panic.downcast_ref::<&str>() { Some(msg) } else if let Some(msg) = panic.downcast_ref::() { - Some(&msg) + Some(msg) } else { None } diff --git a/crates/neon/src/sys/tsfn.rs b/crates/neon/src/sys/tsfn.rs index 885994875..b58b45469 100644 --- a/crates/neon/src/sys/tsfn.rs +++ b/crates/neon/src/sys/tsfn.rs @@ -78,7 +78,7 @@ impl ThreadsafeFunction { Self { tsfn: Tsfn(result.assume_init()), - is_finalized: is_finalized, + is_finalized, callback, } } diff --git a/crates/neon/src/types_impl/buffer/mod.rs b/crates/neon/src/types_impl/buffer/mod.rs index eea40626c..19c20b155 100644 --- a/crates/neon/src/types_impl/buffer/mod.rs +++ b/crates/neon/src/types_impl/buffer/mod.rs @@ -108,7 +108,7 @@ impl<'a, T> Deref for Ref<'a, T> { type Target = [T]; fn deref(&self) -> &Self::Target { - &self.data + self.data } } @@ -116,7 +116,7 @@ impl<'a, T> Deref for RefMut<'a, T> { type Target = [T]; fn deref(&self) -> &Self::Target { - &self.data + self.data } } @@ -129,7 +129,7 @@ impl<'a, T> DerefMut for RefMut<'a, T> { impl<'a, T> Drop for Ref<'a, T> { fn drop(&mut self) { let mut ledger = self.ledger.borrow_mut(); - let range = Ledger::slice_to_range(&self.data); + let range = Ledger::slice_to_range(self.data); let i = ledger.shared.iter().rposition(|r| r == &range).unwrap(); ledger.shared.remove(i); @@ -139,7 +139,7 @@ impl<'a, T> Drop for Ref<'a, T> { impl<'a, T> Drop for RefMut<'a, T> { fn drop(&mut self) { let mut ledger = self.ledger.borrow_mut(); - let range = Ledger::slice_to_range(&self.data); + let range = Ledger::slice_to_range(self.data); let i = ledger.owned.iter().rposition(|r| r == &range).unwrap(); ledger.owned.remove(i); diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index 638c7ffca..7ac4a88d7 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -409,6 +409,9 @@ impl Managed for JsTypedArray { self.0.local } + // This method should be `unsafe` + // https://github.com/neon-bindings/neon/issues/885 + #[allow(clippy::not_unsafe_ptr_arg_deref)] fn from_raw(env: Env, local: raw::Local) -> Self { // Safety: Recomputing this information ensures that the lifetime of the // buffer handle matches the lifetime of the typed array handle. @@ -600,6 +603,7 @@ impl JsTypedArray { /// ```ignore /// self.byte_length() == self.len() * size_of::() /// ``` + #[allow(clippy::len_without_is_empty)] pub fn len<'cx, C>(&self, _cx: &mut C) -> usize where C: Context<'cx>, diff --git a/crates/neon/src/types_impl/error.rs b/crates/neon/src/types_impl/error.rs index e0a67220f..d4e7a002d 100644 --- a/crates/neon/src/types_impl/error.rs +++ b/crates/neon/src/types_impl/error.rs @@ -90,7 +90,7 @@ pub(crate) fn convert_panics NeonResult>( env: Env, f: F, ) -> NeonResult { - match catch_unwind(|| f()) { + match catch_unwind(f) { Ok(result) => result, Err(panic) => { let msg = if let Some(string) = panic.downcast_ref::() { diff --git a/crates/neon/src/types_impl/mod.rs b/crates/neon/src/types_impl/mod.rs index 0a021a765..047d32722 100644 --- a/crates/neon/src/types_impl/mod.rs +++ b/crates/neon/src/types_impl/mod.rs @@ -538,6 +538,7 @@ impl JsArray { unsafe { sys::array::len(env.to_raw(), self.to_raw()) } } + #[allow(clippy::len_without_is_empty)] pub fn len<'a, C: Context<'a>>(&self, cx: &mut C) -> u32 { self.len_inner(cx.env()) } diff --git a/crates/neon/src/types_impl/promise.rs b/crates/neon/src/types_impl/promise.rs index 0aa1d7c00..314c62bfb 100644 --- a/crates/neon/src/types_impl/promise.rs +++ b/crates/neon/src/types_impl/promise.rs @@ -246,7 +246,7 @@ impl Deferred { F: FnOnce(TaskContext) -> JsResult + Send + 'static, { channel.try_send(move |cx| { - self.try_catch_settle(cx, move |cx| complete(cx)); + self.try_catch_settle(cx, complete); Ok(()) }) } diff --git a/test/napi/src/js/futures.rs b/test/napi/src/js/futures.rs index 73b93ce5f..5d2168674 100644 --- a/test/napi/src/js/futures.rs +++ b/test/napi/src/js/futures.rs @@ -8,7 +8,7 @@ fn runtime<'a, C: Context<'a>>(cx: &mut C) -> NeonResult<&'static Runtime> { static RUNTIME: OnceCell = OnceCell::new(); RUNTIME - .get_or_try_init(|| Runtime::new()) + .get_or_try_init(Runtime::new) .or_else(|err| cx.throw_error(&err.to_string())) } @@ -58,7 +58,7 @@ pub fn lazy_async_sum(mut cx: FunctionContext) -> JsResult { let nums = nums .or_throw(&mut cx)? .downcast_or_throw::, _>(&mut cx)? - .as_slice(&mut cx) + .as_slice(&cx) .to_vec(); Ok(nums) diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index 99ed8a44d..4c1653286 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -36,8 +36,8 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { let b_true = cx.boolean(true); let b_false = cx.boolean(false); - assert_eq!(b_true.value(&mut cx), true); - assert_eq!(b_false.value(&mut cx), false); + assert!(b_true.value(&mut cx)); + assert!(!b_false.value(&mut cx)); cx.export_value("undefined", undefined)?; cx.export_value("null", null)?; @@ -81,13 +81,10 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { .abs() < f64::EPSILON ); - assert_eq!( - { - let v: Handle = rust_created.get(&mut cx, "whatever")?; - v.value(&mut cx) - }, - true - ); + assert!({ + let v: Handle = rust_created.get(&mut cx, "whatever")?; + v.value(&mut cx) + }); let property_names = rust_created .get_own_property_names(&mut cx)? From 3f0fc4c157f476acc87c0663375e3f7bd03de31f Mon Sep 17 00:00:00 2001 From: David Herman Date: Fri, 22 Jul 2022 13:55:03 -0700 Subject: [PATCH 18/31] Add tests for detached buffers, and move typed array tests into separate modules --- test/napi/lib/objects.js | 368 ------------------------ test/napi/lib/typedarrays.js | 480 ++++++++++++++++++++++++++++++++ test/napi/src/js/objects.rs | 259 +---------------- test/napi/src/js/typedarrays.rs | 359 ++++++++++++++++++++++++ test/napi/src/lib.rs | 8 +- 5 files changed, 847 insertions(+), 627 deletions(-) create mode 100644 test/napi/lib/typedarrays.js create mode 100644 test/napi/src/js/typedarrays.rs diff --git a/test/napi/lib/objects.js b/test/napi/lib/objects.js index 3512fae71..705fde8b7 100644 --- a/test/napi/lib/objects.js +++ b/test/napi/lib/objects.js @@ -65,374 +65,6 @@ describe("JsObject", function () { }); }); - it("correctly reads a TypedArray using the borrow API", function () { - var b = new ArrayBuffer(32); - var a = new Int32Array(b, 4, 4); - a[0] = 49; - a[1] = 1350; - a[2] = 11; - a[3] = 237; - assert.equal(addon.read_typed_array_with_borrow(a, 0), 49); - assert.equal(addon.read_typed_array_with_borrow(a, 1), 1350); - assert.equal(addon.read_typed_array_with_borrow(a, 2), 11); - assert.equal(addon.read_typed_array_with_borrow(a, 3), 237); - }); - - it("correctly writes to a TypedArray using the borrow_mut API", function () { - var b = new ArrayBuffer(32); - var a = new Int32Array(b, 4, 4); - addon.write_typed_array_with_borrow_mut(a, 0, 43); - assert.equal(a[0], 43); - addon.write_typed_array_with_borrow_mut(a, 1, 1000); - assert.equal(a[1], 1000); - addon.write_typed_array_with_borrow_mut(a, 2, 22); - assert.equal(a[2], 22); - addon.write_typed_array_with_borrow_mut(a, 3, 243); - assert.equal(a[3], 243); - }); - - it("correctly reads a Buffer as a typed array", function () { - var a = Buffer.from([49, 135, 11, 237]); - assert.equal(addon.read_u8_typed_array(a, 0), 49); - assert.equal(addon.read_u8_typed_array(a, 1), 135); - assert.equal(addon.read_u8_typed_array(a, 2), 11); - assert.equal(addon.read_u8_typed_array(a, 3), 237); - }); - - it("copies the contents of one typed array to another", function () { - const a = new Uint32Array([1, 2, 3, 4]); - const b = new Uint32Array(a.length); - - addon.copy_typed_array(a, b); - - assert.deepEqual([...a], [...b]); - }); - - it("cannot borrow overlapping buffers", function () { - const buf = new ArrayBuffer(20); - const arr = new Uint32Array(buf); - const a = new Uint32Array(buf, 4, 2); - const b = new Uint32Array(buf, 8, 2); - - assert.throws(() => addon.copy_typed_array(a, b)); - }); - - it("gets a 16-byte, zeroed ArrayBuffer", function () { - var b = addon.return_array_buffer(); - assert.equal(b.byteLength, 16); - assert.equal(new Uint32Array(b)[0], 0); - assert.equal(new Uint32Array(b)[1], 0); - assert.equal(new Uint32Array(b)[2], 0); - assert.equal(new Uint32Array(b)[3], 0); - }); - - it("correctly reads an ArrayBuffer using the lock API", function () { - var b = new ArrayBuffer(16); - var a = new Uint32Array(b); - a[0] = 47; - a[1] = 133; - a[2] = 9; - a[3] = 88888888; - assert.equal(addon.read_array_buffer_with_lock(a, 0), 47); - assert.equal(addon.read_array_buffer_with_lock(a, 1), 133); - assert.equal(addon.read_array_buffer_with_lock(a, 2), 9); - assert.equal(addon.read_array_buffer_with_lock(a, 3), 88888888); - }); - - it("correctly reads an ArrayBuffer using the borrow API", function () { - var b = new ArrayBuffer(4); - var a = new Uint8Array(b); - a[0] = 49; - a[1] = 135; - a[2] = 11; - a[3] = 237; - assert.equal(addon.read_array_buffer_with_borrow(b, 0), 49); - assert.equal(addon.read_array_buffer_with_borrow(b, 1), 135); - assert.equal(addon.read_array_buffer_with_borrow(b, 2), 11); - assert.equal(addon.read_array_buffer_with_borrow(b, 3), 237); - }); - - it("correctly writes to an ArrayBuffer using the lock API", function () { - var b = new ArrayBuffer(16); - addon.write_array_buffer_with_lock(b, 0, 3); - assert.equal(new Uint8Array(b)[0], 3); - addon.write_array_buffer_with_lock(b, 1, 42); - assert.equal(new Uint8Array(b)[1], 42); - addon.write_array_buffer_with_lock(b, 2, 127); - assert.equal(new Uint8Array(b)[2], 127); - addon.write_array_buffer_with_lock(b, 3, 255); - assert.equal(new Uint8Array(b)[3], 255); - }); - - it("correctly writes to an ArrayBuffer using the borrow_mut API", function () { - var b = new ArrayBuffer(4); - addon.write_array_buffer_with_borrow_mut(b, 0, 43); - assert.equal(new Uint8Array(b)[0], 43); - addon.write_array_buffer_with_borrow_mut(b, 1, 100); - assert.equal(new Uint8Array(b)[1], 100); - addon.write_array_buffer_with_borrow_mut(b, 2, 22); - assert.equal(new Uint8Array(b)[2], 22); - addon.write_array_buffer_with_borrow_mut(b, 3, 243); - assert.equal(new Uint8Array(b)[3], 243); - }); - - it("gets a 16-byte, uninitialized Buffer", function () { - var b = addon.return_uninitialized_buffer(); - assert.ok(b.length === 16); - }); - - it("gets a 16-byte, zeroed Buffer", function () { - var b = addon.return_buffer(); - assert.ok(b.equals(Buffer.alloc(16))); - }); - - it("gets an external Buffer", function () { - var expected = "String to copy"; - var buf = addon.return_external_buffer(expected); - assert.instanceOf(buf, Buffer); - assert.strictEqual(buf.toString(), expected); - }); - - it("gets an external ArrayBuffer", function () { - var expected = "String to copy"; - var buf = addon.return_external_array_buffer(expected); - assert.instanceOf(buf, ArrayBuffer); - assert.strictEqual(Buffer.from(buf).toString(), expected); - }); - - it("gets a typed array constructed from an ArrayBuffer", function () { - var b = new ArrayBuffer(64); - var i8 = addon.return_int8array_from_arraybuffer(b); - assert.strictEqual(i8.byteLength, 64); - assert.strictEqual(i8.length, 64); - i8[0] = 0x17; - i8[1] = -0x17; - assert.deepEqual([...i8.slice(0, 2)], [0x17, -0x17]); - - var b = new ArrayBuffer(64); - var i16 = addon.return_int16array_from_arraybuffer(b); - assert.strictEqual(i16.byteLength, 64); - assert.strictEqual(i16.length, 32); - i16[0] = 0x1234; - i16[1] = -1; - i16[2] = -2; - i16[3] = 0x5678; - assert.deepEqual([...i16.slice(0, 4)], [0x1234, -1, -2, 0x5678]); - var u8 = new Uint8Array(b); - assert.deepEqual( - [...u8.slice(0, 8)], - [0x34, 0x12, 0xff, 0xff, 0xfe, 0xff, 0x78, 0x56] - ); - - var b = new ArrayBuffer(64); - var u32 = addon.return_uint32array_from_arraybuffer(b); - assert.strictEqual(u32.byteLength, 64); - assert.strictEqual(u32.length, 16); - u32[0] = 0x12345678; - var u8 = new Uint8Array(b); - assert.deepEqual([...u8.slice(0, 4)], [0x78, 0x56, 0x34, 0x12]); - - var b = new ArrayBuffer(64); - var f64 = addon.return_float64array_from_arraybuffer(b); - assert.strictEqual(f64.byteLength, 64); - assert.strictEqual(f64.length, 8); - f64[0] = 1.0; - f64[1] = 2.0; - f64[2] = 3.141592653589793; - assert.deepEqual([...f64.slice(0, 3)], [1.0, 2.0, 3.141592653589793]); - assert.deepEqual( - [...new Float64Array(b).slice(0, 3)], - [1.0, 2.0, 3.141592653589793] - ); - - var b = new ArrayBuffer(64); - var u64 = addon.return_biguint64array_from_arraybuffer(b); - assert.strictEqual(u64.byteLength, 64); - assert.strictEqual(u64.length, 8); - u64[0] = 0x1234567887654321n; - u64[1] = 0xcafed00d1337c0den; - var u8 = new Uint8Array(b); - assert.deepEqual( - [...u64.slice(0, 2)], - [0x1234567887654321n, 0xcafed00d1337c0den] - ); - assert.deepEqual( - [...u8.slice(0, 16)], - [ - 0x21, 0x43, 0x65, 0x87, 0x78, 0x56, 0x34, 0x12, 0xde, 0xc0, 0x37, 0x13, - 0x0d, 0xd0, 0xfe, 0xca, - ] - ); - }); - - it("gets a new typed array", function () { - var i32 = addon.return_new_int32array(16); - assert.strictEqual(i32.constructor, Int32Array); - assert.strictEqual(i32.byteLength, 64); - assert.strictEqual(i32.length, 16); - assert.deepEqual( - [...i32], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - ); - }); - - it("gets correct typed array info", function () { - var buf = new ArrayBuffer(128); - - var a = addon.return_int8array_from_arraybuffer(buf); - var info = addon.get_typed_array_info(a); - - assert.strictEqual(buf, a.buffer); - assert.strictEqual(0, a.byteOffset); - assert.strictEqual(128, a.length); - assert.strictEqual(128, a.byteLength); - - assert.strictEqual(buf, info.buffer); - assert.strictEqual(0, info.byteOffset); - assert.strictEqual(128, info.length); - assert.strictEqual(128, info.byteLength); - - var a = addon.return_int16array_from_arraybuffer(buf); - var info = addon.get_typed_array_info(a); - - assert.strictEqual(buf, a.buffer); - assert.strictEqual(0, a.byteOffset); - assert.strictEqual(64, a.length); - assert.strictEqual(128, a.byteLength); - - assert.strictEqual(buf, info.buffer); - assert.strictEqual(0, info.byteOffset); - assert.strictEqual(64, info.length); - assert.strictEqual(128, info.byteLength); - - var a = addon.return_uint32array_from_arraybuffer(buf); - var info = addon.get_typed_array_info(a); - - assert.strictEqual(buf, a.buffer); - assert.strictEqual(0, a.byteOffset); - assert.strictEqual(32, a.length); - assert.strictEqual(128, a.byteLength); - - assert.strictEqual(buf, info.buffer); - assert.strictEqual(0, info.byteOffset); - assert.strictEqual(32, info.length); - assert.strictEqual(128, info.byteLength); - - var a = addon.return_biguint64array_from_arraybuffer(buf); - var info = addon.get_typed_array_info(a); - - assert.strictEqual(buf, a.buffer); - assert.strictEqual(0, a.byteOffset); - assert.strictEqual(16, a.length); - assert.strictEqual(128, a.byteLength); - - assert.strictEqual(buf, info.buffer); - assert.strictEqual(0, info.byteOffset); - assert.strictEqual(16, info.length); - assert.strictEqual(128, info.byteLength); - }); - - it("correctly constructs a view over a slice of a buffer", function () { - var buf = new ArrayBuffer(128); - - var a = addon.return_uint32array_from_arraybuffer_region(buf, 16, 4); - var info = addon.get_typed_array_info(a); - - assert.strictEqual(buf, a.buffer); - assert.strictEqual(16, a.byteOffset); - assert.strictEqual(4, a.length); - assert.strictEqual(16, a.byteLength); - - assert.strictEqual(buf, info.buffer); - assert.strictEqual(16, info.byteOffset); - assert.strictEqual(4, info.length); - assert.strictEqual(16, info.byteLength); - - a[0] = 17; - a[1] = 42; - a[2] = 100; - a[3] = 1000; - - var left = buf.slice(0, 16); - var middle = buf.slice(16, 32); - var right = buf.slice(32); - - assert.deepEqual(new Uint8Array(16), new Uint8Array(left)); - assert.deepEqual( - new Uint8Array([17, 0, 0, 0, 42, 0, 0, 0, 100, 0, 0, 0, 232, 3, 0, 0]), - new Uint8Array(middle) - ); - assert.deepEqual(new Uint8Array(96), new Uint8Array(right)); - }); - - it("properly fails to construct typed arrays with invalid arguments", function () { - var buf = new ArrayBuffer(32); - try { - addon.return_uint32array_from_arraybuffer_region(buf, 1, 4); - assert.fail("should have thrown for unaligned offset"); - } catch (expected) {} - - try { - addon.return_uint32array_from_arraybuffer_region(buf, 100, 104); - assert.fail("should have thrown for bounds check failure"); - } catch (expected) {} - - try { - addon.return_uint32array_from_arraybuffer_region(buf, 0, 5); - assert.fail("should have thrown for invalid length"); - } catch (expected) {} - - try { - addon.return_uint32array_from_arraybuffer_region(buf, 0, 10); - assert.fail("should have thrown for excessive length"); - } catch (expected) {} - }); - - it("correctly reads a Buffer using the lock API", function () { - var b = Buffer.allocUnsafe(16); - b.writeUInt8(147, 0); - b.writeUInt8(113, 1); - b.writeUInt8(109, 2); - b.writeUInt8(189, 3); - assert.equal(addon.read_buffer_with_lock(b, 0), 147); - assert.equal(addon.read_buffer_with_lock(b, 1), 113); - assert.equal(addon.read_buffer_with_lock(b, 2), 109); - assert.equal(addon.read_buffer_with_lock(b, 3), 189); - }); - - it("correctly reads a Buffer using the borrow API", function () { - var b = Buffer.from([149, 224, 70, 229]); - assert.equal(addon.read_buffer_with_borrow(b, 0), 149); - assert.equal(addon.read_buffer_with_borrow(b, 1), 224); - assert.equal(addon.read_buffer_with_borrow(b, 2), 70); - assert.equal(addon.read_buffer_with_borrow(b, 3), 229); - }); - - it("correctly writes to a Buffer using the lock API", function () { - var b = Buffer.allocUnsafe(16); - b.fill(0); - addon.write_buffer_with_lock(b, 0, 6); - assert.equal(b.readUInt8(0), 6); - addon.write_buffer_with_lock(b, 1, 61); - assert.equal(b.readUInt8(1), 61); - addon.write_buffer_with_lock(b, 2, 45); - assert.equal(b.readUInt8(2), 45); - addon.write_buffer_with_lock(b, 3, 216); - assert.equal(b.readUInt8(3), 216); - }); - - it("correctly writes to a Buffer using the borrow_mut API", function () { - var b = Buffer.alloc(4); - addon.write_buffer_with_borrow_mut(b, 0, 16); - assert.equal(b[0], 16); - addon.write_buffer_with_borrow_mut(b, 1, 100); - assert.equal(b[1], 100); - addon.write_buffer_with_borrow_mut(b, 2, 232); - assert.equal(b[2], 232); - addon.write_buffer_with_borrow_mut(b, 3, 55); - assert.equal(b[3], 55); - }); - it("returns only own properties from get_own_property_names", function () { var superObject = { a: 1, diff --git a/test/napi/lib/typedarrays.js b/test/napi/lib/typedarrays.js new file mode 100644 index 000000000..c9ae40348 --- /dev/null +++ b/test/napi/lib/typedarrays.js @@ -0,0 +1,480 @@ +var addon = require(".."); +var assert = require("chai").assert; + +const { Worker, isMainThread, parentPort } = require('worker_threads'); + +if (!isMainThread) { + parentPort.on('message', (message) => { + // transfer it back + parentPort.postMessage(message, [message]); + }); + + return; +} + +// A background thread we can transfer buffers to as a way to force +// them to be detached (see the `detach` function). +const DETACH_WORKER = new Worker(__filename); + +// Allow the test harness to spin down the background thread and exit +// when the main thread completes. +DETACH_WORKER.unref(); + +function detach(buffer) { + if (!(buffer instanceof ArrayBuffer)) { + throw new TypeError(); + } + + DETACH_WORKER.postMessage(buffer, [buffer]); + + let resolve, reject; + + let promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + + DETACH_WORKER.once('message', (message) => { + resolve(message); + }); + + return promise; +}; + +describe("Typed arrays", function () { + it("correctly reads a TypedArray using the borrow API", function () { + var b = new ArrayBuffer(32); + var a = new Int32Array(b, 4, 4); + a[0] = 49; + a[1] = 1350; + a[2] = 11; + a[3] = 237; + assert.equal(addon.read_typed_array_with_borrow(a, 0), 49); + assert.equal(addon.read_typed_array_with_borrow(a, 1), 1350); + assert.equal(addon.read_typed_array_with_borrow(a, 2), 11); + assert.equal(addon.read_typed_array_with_borrow(a, 3), 237); + }); + + it("correctly writes to a TypedArray using the borrow_mut API", function () { + var b = new ArrayBuffer(32); + var a = new Int32Array(b, 4, 4); + addon.write_typed_array_with_borrow_mut(a, 0, 43); + assert.equal(a[0], 43); + addon.write_typed_array_with_borrow_mut(a, 1, 1000); + assert.equal(a[1], 1000); + addon.write_typed_array_with_borrow_mut(a, 2, 22); + assert.equal(a[2], 22); + addon.write_typed_array_with_borrow_mut(a, 3, 243); + assert.equal(a[3], 243); + }); + + it("correctly reads a Buffer as a typed array", function () { + var a = Buffer.from([49, 135, 11, 237]); + assert.equal(addon.read_u8_typed_array(a, 0), 49); + assert.equal(addon.read_u8_typed_array(a, 1), 135); + assert.equal(addon.read_u8_typed_array(a, 2), 11); + assert.equal(addon.read_u8_typed_array(a, 3), 237); + }); + + it("copies the contents of one typed array to another", function () { + const a = new Uint32Array([1, 2, 3, 4]); + const b = new Uint32Array(a.length); + + addon.copy_typed_array(a, b); + + assert.deepEqual([...a], [...b]); + }); + + it("cannot borrow overlapping buffers", function () { + const buf = new ArrayBuffer(20); + const arr = new Uint32Array(buf); + const a = new Uint32Array(buf, 4, 2); + const b = new Uint32Array(buf, 8, 2); + + assert.throws(() => addon.copy_typed_array(a, b)); + }); + + it("gets a 16-byte, zeroed ArrayBuffer", function () { + var b = addon.return_array_buffer(); + assert.equal(b.byteLength, 16); + assert.equal(new Uint32Array(b)[0], 0); + assert.equal(new Uint32Array(b)[1], 0); + assert.equal(new Uint32Array(b)[2], 0); + assert.equal(new Uint32Array(b)[3], 0); + }); + + it("correctly reads an ArrayBuffer using the lock API", function () { + var b = new ArrayBuffer(16); + var a = new Uint32Array(b); + a[0] = 47; + a[1] = 133; + a[2] = 9; + a[3] = 88888888; + assert.equal(addon.read_array_buffer_with_lock(a, 0), 47); + assert.equal(addon.read_array_buffer_with_lock(a, 1), 133); + assert.equal(addon.read_array_buffer_with_lock(a, 2), 9); + assert.equal(addon.read_array_buffer_with_lock(a, 3), 88888888); + }); + + it("correctly reads an ArrayBuffer using the borrow API", function () { + var b = new ArrayBuffer(4); + var a = new Uint8Array(b); + a[0] = 49; + a[1] = 135; + a[2] = 11; + a[3] = 237; + assert.equal(addon.read_array_buffer_with_borrow(b, 0), 49); + assert.equal(addon.read_array_buffer_with_borrow(b, 1), 135); + assert.equal(addon.read_array_buffer_with_borrow(b, 2), 11); + assert.equal(addon.read_array_buffer_with_borrow(b, 3), 237); + }); + + it("correctly writes to an ArrayBuffer using the lock API", function () { + var b = new ArrayBuffer(16); + addon.write_array_buffer_with_lock(b, 0, 3); + assert.equal(new Uint8Array(b)[0], 3); + addon.write_array_buffer_with_lock(b, 1, 42); + assert.equal(new Uint8Array(b)[1], 42); + addon.write_array_buffer_with_lock(b, 2, 127); + assert.equal(new Uint8Array(b)[2], 127); + addon.write_array_buffer_with_lock(b, 3, 255); + assert.equal(new Uint8Array(b)[3], 255); + }); + + it("correctly writes to an ArrayBuffer using the borrow_mut API", function () { + var b = new ArrayBuffer(4); + addon.write_array_buffer_with_borrow_mut(b, 0, 43); + assert.equal(new Uint8Array(b)[0], 43); + addon.write_array_buffer_with_borrow_mut(b, 1, 100); + assert.equal(new Uint8Array(b)[1], 100); + addon.write_array_buffer_with_borrow_mut(b, 2, 22); + assert.equal(new Uint8Array(b)[2], 22); + addon.write_array_buffer_with_borrow_mut(b, 3, 243); + assert.equal(new Uint8Array(b)[3], 243); + }); + + it("gets a 16-byte, uninitialized Buffer", function () { + var b = addon.return_uninitialized_buffer(); + assert.ok(b.length === 16); + }); + + it("gets a 16-byte, zeroed Buffer", function () { + var b = addon.return_buffer(); + assert.ok(b.equals(Buffer.alloc(16))); + }); + + it("gets an external Buffer", function () { + var expected = "String to copy"; + var buf = addon.return_external_buffer(expected); + assert.instanceOf(buf, Buffer); + assert.strictEqual(buf.toString(), expected); + }); + + it("gets an external ArrayBuffer", function () { + var expected = "String to copy"; + var buf = addon.return_external_array_buffer(expected); + assert.instanceOf(buf, ArrayBuffer); + assert.strictEqual(Buffer.from(buf).toString(), expected); + }); + + it("gets a typed array constructed from an ArrayBuffer", function () { + var b = new ArrayBuffer(64); + var i8 = addon.return_int8array_from_arraybuffer(b); + assert.strictEqual(i8.byteLength, 64); + assert.strictEqual(i8.length, 64); + i8[0] = 0x17; + i8[1] = -0x17; + assert.deepEqual([...i8.slice(0, 2)], [0x17, -0x17]); + + var b = new ArrayBuffer(64); + var i16 = addon.return_int16array_from_arraybuffer(b); + assert.strictEqual(i16.byteLength, 64); + assert.strictEqual(i16.length, 32); + i16[0] = 0x1234; + i16[1] = -1; + i16[2] = -2; + i16[3] = 0x5678; + assert.deepEqual([...i16.slice(0, 4)], [0x1234, -1, -2, 0x5678]); + var u8 = new Uint8Array(b); + assert.deepEqual( + [...u8.slice(0, 8)], + [0x34, 0x12, 0xff, 0xff, 0xfe, 0xff, 0x78, 0x56] + ); + + var b = new ArrayBuffer(64); + var u32 = addon.return_uint32array_from_arraybuffer(b); + assert.strictEqual(u32.byteLength, 64); + assert.strictEqual(u32.length, 16); + u32[0] = 0x12345678; + var u8 = new Uint8Array(b); + assert.deepEqual([...u8.slice(0, 4)], [0x78, 0x56, 0x34, 0x12]); + + var b = new ArrayBuffer(64); + var f64 = addon.return_float64array_from_arraybuffer(b); + assert.strictEqual(f64.byteLength, 64); + assert.strictEqual(f64.length, 8); + f64[0] = 1.0; + f64[1] = 2.0; + f64[2] = 3.141592653589793; + assert.deepEqual([...f64.slice(0, 3)], [1.0, 2.0, 3.141592653589793]); + assert.deepEqual( + [...new Float64Array(b).slice(0, 3)], + [1.0, 2.0, 3.141592653589793] + ); + + var b = new ArrayBuffer(64); + var u64 = addon.return_biguint64array_from_arraybuffer(b); + assert.strictEqual(u64.byteLength, 64); + assert.strictEqual(u64.length, 8); + u64[0] = 0x1234567887654321n; + u64[1] = 0xcafed00d1337c0den; + var u8 = new Uint8Array(b); + assert.deepEqual( + [...u64.slice(0, 2)], + [0x1234567887654321n, 0xcafed00d1337c0den] + ); + assert.deepEqual( + [...u8.slice(0, 16)], + [ + 0x21, 0x43, 0x65, 0x87, 0x78, 0x56, 0x34, 0x12, 0xde, 0xc0, 0x37, 0x13, + 0x0d, 0xd0, 0xfe, 0xca, + ] + ); + }); + + it("gets a new typed array", function () { + var i32 = addon.return_new_int32array(16); + assert.strictEqual(i32.constructor, Int32Array); + assert.strictEqual(i32.byteLength, 64); + assert.strictEqual(i32.length, 16); + assert.deepEqual( + [...i32], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ); + }); + + it("gets correct typed array info", function () { + var buf = new ArrayBuffer(128); + + var a = addon.return_int8array_from_arraybuffer(buf); + var info = addon.get_typed_array_info(a); + + assert.strictEqual(buf, a.buffer); + assert.strictEqual(0, a.byteOffset); + assert.strictEqual(128, a.length); + assert.strictEqual(128, a.byteLength); + + assert.strictEqual(buf, info.buffer); + assert.strictEqual(0, info.byteOffset); + assert.strictEqual(128, info.length); + assert.strictEqual(128, info.byteLength); + + var a = addon.return_int16array_from_arraybuffer(buf); + var info = addon.get_typed_array_info(a); + + assert.strictEqual(buf, a.buffer); + assert.strictEqual(0, a.byteOffset); + assert.strictEqual(64, a.length); + assert.strictEqual(128, a.byteLength); + + assert.strictEqual(buf, info.buffer); + assert.strictEqual(0, info.byteOffset); + assert.strictEqual(64, info.length); + assert.strictEqual(128, info.byteLength); + + var a = addon.return_uint32array_from_arraybuffer(buf); + var info = addon.get_typed_array_info(a); + + assert.strictEqual(buf, a.buffer); + assert.strictEqual(0, a.byteOffset); + assert.strictEqual(32, a.length); + assert.strictEqual(128, a.byteLength); + + assert.strictEqual(buf, info.buffer); + assert.strictEqual(0, info.byteOffset); + assert.strictEqual(32, info.length); + assert.strictEqual(128, info.byteLength); + + var a = addon.return_biguint64array_from_arraybuffer(buf); + var info = addon.get_typed_array_info(a); + + assert.strictEqual(buf, a.buffer); + assert.strictEqual(0, a.byteOffset); + assert.strictEqual(16, a.length); + assert.strictEqual(128, a.byteLength); + + assert.strictEqual(buf, info.buffer); + assert.strictEqual(0, info.byteOffset); + assert.strictEqual(16, info.length); + assert.strictEqual(128, info.byteLength); + }); + + it("correctly constructs a view over a slice of a buffer", function () { + var buf = new ArrayBuffer(128); + + var a = addon.return_uint32array_from_arraybuffer_region(buf, 16, 4); + var info = addon.get_typed_array_info(a); + + assert.strictEqual(buf, a.buffer); + assert.strictEqual(16, a.byteOffset); + assert.strictEqual(4, a.length); + assert.strictEqual(16, a.byteLength); + + assert.strictEqual(buf, info.buffer); + assert.strictEqual(16, info.byteOffset); + assert.strictEqual(4, info.length); + assert.strictEqual(16, info.byteLength); + + a[0] = 17; + a[1] = 42; + a[2] = 100; + a[3] = 1000; + + var left = buf.slice(0, 16); + var middle = buf.slice(16, 32); + var right = buf.slice(32); + + assert.deepEqual(new Uint8Array(16), new Uint8Array(left)); + assert.deepEqual( + new Uint8Array([17, 0, 0, 0, 42, 0, 0, 0, 100, 0, 0, 0, 232, 3, 0, 0]), + new Uint8Array(middle) + ); + assert.deepEqual(new Uint8Array(96), new Uint8Array(right)); + }); + + it("properly fails to construct typed arrays with invalid arguments", function () { + var buf = new ArrayBuffer(32); + try { + addon.return_uint32array_from_arraybuffer_region(buf, 1, 4); + assert.fail("should have thrown for unaligned offset"); + } catch (expected) {} + + try { + addon.return_uint32array_from_arraybuffer_region(buf, 100, 104); + assert.fail("should have thrown for bounds check failure"); + } catch (expected) {} + + try { + addon.return_uint32array_from_arraybuffer_region(buf, 0, 5); + assert.fail("should have thrown for invalid length"); + } catch (expected) {} + + try { + addon.return_uint32array_from_arraybuffer_region(buf, 0, 10); + assert.fail("should have thrown for excessive length"); + } catch (expected) {} + }); + + it("correctly reads a Buffer using the lock API", function () { + var b = Buffer.allocUnsafe(16); + b.writeUInt8(147, 0); + b.writeUInt8(113, 1); + b.writeUInt8(109, 2); + b.writeUInt8(189, 3); + assert.equal(addon.read_buffer_with_lock(b, 0), 147); + assert.equal(addon.read_buffer_with_lock(b, 1), 113); + assert.equal(addon.read_buffer_with_lock(b, 2), 109); + assert.equal(addon.read_buffer_with_lock(b, 3), 189); + }); + + it("correctly reads a Buffer using the borrow API", function () { + var b = Buffer.from([149, 224, 70, 229]); + assert.equal(addon.read_buffer_with_borrow(b, 0), 149); + assert.equal(addon.read_buffer_with_borrow(b, 1), 224); + assert.equal(addon.read_buffer_with_borrow(b, 2), 70); + assert.equal(addon.read_buffer_with_borrow(b, 3), 229); + }); + + it("correctly writes to a Buffer using the lock API", function () { + var b = Buffer.allocUnsafe(16); + b.fill(0); + addon.write_buffer_with_lock(b, 0, 6); + assert.equal(b.readUInt8(0), 6); + addon.write_buffer_with_lock(b, 1, 61); + assert.equal(b.readUInt8(1), 61); + addon.write_buffer_with_lock(b, 2, 45); + assert.equal(b.readUInt8(2), 45); + addon.write_buffer_with_lock(b, 3, 216); + assert.equal(b.readUInt8(3), 216); + }); + + it("correctly writes to a Buffer using the borrow_mut API", function () { + var b = Buffer.alloc(4); + addon.write_buffer_with_borrow_mut(b, 0, 16); + assert.equal(b[0], 16); + addon.write_buffer_with_borrow_mut(b, 1, 100); + assert.equal(b[1], 100); + addon.write_buffer_with_borrow_mut(b, 2, 232); + assert.equal(b[2], 232); + addon.write_buffer_with_borrow_mut(b, 3, 55); + assert.equal(b[3], 55); + }); + + it("zeroes the byteLength when an ArrayBuffer is detached", function () { + var buf = new ArrayBuffer(16); + assert.strictEqual(buf.byteLength, 16); + assert.strictEqual(addon.get_arraybuffer_byte_length(buf), 16); + + detach(buf); + + assert.strictEqual(buf.byteLength, 0); + assert.strictEqual(addon.get_arraybuffer_byte_length(buf), 0); + }); + + it("provides correct metadata when detaching a typed array's buffer", function () { + var buf = new ArrayBuffer(16); + var arr = new Uint32Array(buf); + + assert.strictEqual(buf.byteLength, 16); + assert.strictEqual(arr.byteLength, 16); + assert.strictEqual(arr.length, 4); + assert.strictEqual(addon.get_typed_array_info(arr).byteLength, 16); + assert.strictEqual(addon.get_typed_array_info(arr).length, 4); + assert.strictEqual(addon.get_typed_array_info(arr).buffer, buf); + + let { before, after } = addon.detach_same_handle(arr, (arr) => detach(arr.buffer)); + + assert.strictEqual(before.byteLength, 16); + assert.strictEqual(before.length, 4); + + assert.strictEqual(arr.byteLength, 0); + assert.strictEqual(arr.length, 0); + assert.strictEqual(addon.get_typed_array_info(arr).byteLength, 0); + assert.strictEqual(addon.get_typed_array_info(arr).length, 0); + + assert.strictEqual(after.byteLength, 0); + assert.strictEqual(after.length, 0); + }); + + it("provides correct metadata when detaching an escaped typed array's buffer", function () { + let { before, after } = addon.detach_and_escape((arr) => detach(arr.buffer)); + assert.strictEqual(before.byteLength, 16); + assert.strictEqual(before.length, 4); + assert.strictEqual(after.byteLength, 0); + assert.strictEqual(after.length, 0); + }); + + it("provides correct metadata when detaching a casted typed array's buffer", function () { + var buf = new ArrayBuffer(16); + var arr = new Uint32Array(buf); + + let { before, after } = addon.detach_and_cast(arr, (arr) => detach(arr.buffer)); + + assert.strictEqual(before.byteLength, 16); + assert.strictEqual(before.length, 4); + assert.strictEqual(after.byteLength, 0); + assert.strictEqual(after.length, 0); + }); + + it("provides correct metadata when detaching an un-rooted typed array's buffer", function () { + var buf = new ArrayBuffer(16); + var arr = new Uint32Array(buf); + + let { before, after } = addon.detach_and_unroot(arr, (arr) => detach(arr.buffer)); + + assert.strictEqual(before.byteLength, 16); + assert.strictEqual(before.length, 4); + assert.strictEqual(after.byteLength, 0); + assert.strictEqual(after.length, 0); + }); +}); diff --git a/test/napi/src/js/objects.rs b/test/napi/src/js/objects.rs index c9624adcf..f5af8f8fb 100644 --- a/test/napi/src/js/objects.rs +++ b/test/napi/src/js/objects.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use neon::{ prelude::*, - types::buffer::{Binary, BorrowError, TypedArray}, + types::buffer::TypedArray, }; pub fn return_js_global_object(mut cx: FunctionContext) -> JsResult { @@ -52,263 +52,6 @@ pub fn seal_js_object(mut cx: FunctionContext) -> JsResult { } } -pub fn return_array_buffer(mut cx: FunctionContext) -> JsResult { - let b: Handle = cx.array_buffer(16)?; - Ok(b) -} - -pub fn read_array_buffer_with_lock(mut cx: FunctionContext) -> JsResult { - let buf = cx.argument::>(0)?; - let i = cx.argument::(1)?.value(&mut cx) as usize; - let lock = cx.lock(); - let n = buf.try_borrow(&lock).map(|buf| buf[i]).or_throw(&mut cx)?; - - Ok(cx.number(n)) -} - -pub fn read_array_buffer_with_borrow(mut cx: FunctionContext) -> JsResult { - let buf = cx.argument::(0)?; - let i = cx.argument::(1)?.value(&mut cx) as usize; - let n = buf.as_slice(&cx)[i]; - - Ok(cx.number(n as f64)) -} - -pub fn write_array_buffer_with_lock(mut cx: FunctionContext) -> JsResult { - let mut b: Handle = cx.argument(0)?; - let i = cx.argument::(1)?.value(&mut cx) as u32 as usize; - let x = cx.argument::(2)?.value(&mut cx) as u8; - let lock = cx.lock(); - - b.try_borrow_mut(&lock) - .map(|mut slice| { - slice[i] = x; - }) - .or_throw(&mut cx)?; - - Ok(cx.undefined()) -} - -pub fn write_array_buffer_with_borrow_mut(mut cx: FunctionContext) -> JsResult { - let mut buf = cx.argument::(0)?; - let i = cx.argument::(1)?.value(&mut cx) as usize; - let n = cx.argument::(2)?.value(&mut cx) as u8; - - buf.as_mut_slice(&mut cx)[i] = n; - - Ok(cx.undefined()) -} - -pub fn read_typed_array_with_borrow(mut cx: FunctionContext) -> JsResult { - let buf = cx.argument::>(0)?; - let i = cx.argument::(1)?.value(&mut cx) as usize; - let n = buf.as_slice(&cx)[i]; - - Ok(cx.number(n as f64)) -} - -pub fn write_typed_array_with_borrow_mut(mut cx: FunctionContext) -> JsResult { - let mut buf = cx.argument::>(0)?; - let i = cx.argument::(1)?.value(&mut cx) as usize; - let n = cx.argument::(2)?.value(&mut cx) as i32; - - buf.as_mut_slice(&mut cx)[i] = n; - - Ok(cx.undefined()) -} - -pub fn read_u8_typed_array(mut cx: FunctionContext) -> JsResult { - let buf = cx.argument::>(0)?; - let i = cx.argument::(1)?.value(&mut cx) as usize; - let n = buf.as_slice(&cx)[i]; - - Ok(cx.number(n as f64)) -} - -pub fn copy_typed_array(mut cx: FunctionContext) -> JsResult { - let source = cx.argument::>(0)?; - let mut dest = cx.argument::>(1)?; - let mut run = || -> Result<_, BorrowError> { - let lock = cx.lock(); - let source = source.try_borrow(&lock)?; - let mut dest = dest.try_borrow_mut(&lock)?; - - dest.copy_from_slice(&source); - - Ok(()) - }; - - run().or_throw(&mut cx)?; - - Ok(cx.undefined()) -} - -pub fn return_uninitialized_buffer(mut cx: FunctionContext) -> JsResult { - let b: Handle = unsafe { JsBuffer::uninitialized(&mut cx, 16)? }; - Ok(b) -} - -pub fn return_buffer(mut cx: FunctionContext) -> JsResult { - let b: Handle = cx.buffer(16)?; - Ok(b) -} - -pub fn return_external_buffer(mut cx: FunctionContext) -> JsResult { - let data = cx.argument::(0)?.value(&mut cx); - let buf = JsBuffer::external(&mut cx, data.into_bytes()); - - Ok(buf) -} - -pub fn return_external_array_buffer(mut cx: FunctionContext) -> JsResult { - let data = cx.argument::(0)?.value(&mut cx); - let buf = JsArrayBuffer::external(&mut cx, data.into_bytes()); - - Ok(buf) -} - -pub fn return_int8array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { - let buf = cx.argument::(0)?; - JsInt8Array::from_buffer(&mut cx, buf) -} - -pub fn return_int16array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { - let buf = cx.argument::(0)?; - JsInt16Array::from_buffer(&mut cx, buf) -} - -pub fn return_uint32array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { - let buf = cx.argument::(0)?; - JsUint32Array::from_buffer(&mut cx, buf) -} - -pub fn return_float64array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { - let buf = cx.argument::(0)?; - JsFloat64Array::from_buffer(&mut cx, buf) -} - -pub fn return_biguint64array_from_arraybuffer( - mut cx: FunctionContext, -) -> JsResult { - let buf = cx.argument::(0)?; - JsBigUint64Array::from_buffer(&mut cx, buf) -} - -pub fn return_new_int32array(mut cx: FunctionContext) -> JsResult { - let len = cx.argument::(0)?.value(&mut cx) as usize; - JsInt32Array::new(&mut cx, len) -} - -pub fn return_uint32array_from_arraybuffer_region( - mut cx: FunctionContext, -) -> JsResult { - let buf = cx.argument::(0)?; - let byte_offset = cx.argument::(1)?; - let byte_offset = byte_offset.value(&mut cx); - let len = cx.argument::(2)?; - let len = len.value(&mut cx); - JsUint32Array::from_buffer_region(&mut cx, buf, byte_offset as usize, len as usize) -} - -fn typed_array_info<'cx, C, T: Binary>( - cx: &mut C, - a: Handle<'cx, JsTypedArray>, -) -> JsResult<'cx, JsObject> -where - C: Context<'cx>, -{ - let byte_offset = a.byte_offset(cx); - let byte_offset = cx.number(byte_offset as u32); - - let len = a.len(cx); - let len = cx.number(len as u32); - - let byte_length = a.byte_length(cx); - let byte_length = cx.number(byte_length as u32); - - let buffer = a.buffer(cx); - - let obj = cx.empty_object(); - - obj.set(cx, "byteOffset", byte_offset)?; - obj.set(cx, "length", len)?; - obj.set(cx, "byteLength", byte_length)?; - obj.set(cx, "buffer", buffer)?; - - Ok(obj) -} - -pub fn get_typed_array_info(mut cx: FunctionContext) -> JsResult { - let x = cx.argument::(0)?; - - if let Ok(a) = x.downcast::, _>(&mut cx) { - typed_array_info(&mut cx, a) - } else if let Ok(a) = x.downcast::, _>(&mut cx) { - typed_array_info(&mut cx, a) - } else if let Ok(a) = x.downcast::, _>(&mut cx) { - typed_array_info(&mut cx, a) - } else if let Ok(a) = x.downcast::, _>(&mut cx) { - typed_array_info(&mut cx, a) - } else if let Ok(a) = x.downcast::, _>(&mut cx) { - typed_array_info(&mut cx, a) - } else if let Ok(a) = x.downcast::, _>(&mut cx) { - typed_array_info(&mut cx, a) - } else if let Ok(a) = x.downcast::, _>(&mut cx) { - typed_array_info(&mut cx, a) - } else if let Ok(a) = x.downcast::, _>(&mut cx) { - typed_array_info(&mut cx, a) - } else if let Ok(a) = x.downcast::, _>(&mut cx) { - typed_array_info(&mut cx, a) - } else if let Ok(a) = x.downcast::, _>(&mut cx) { - typed_array_info(&mut cx, a) - } else { - cx.throw_type_error("expected a typed array") - } -} - -pub fn read_buffer_with_lock(mut cx: FunctionContext) -> JsResult { - let b: Handle = cx.argument(0)?; - let i = cx.argument::(1)?.value(&mut cx) as usize; - let lock = cx.lock(); - let x = b - .try_borrow(&lock) - .map(|slice| slice[i]) - .or_throw(&mut cx)?; - - Ok(cx.number(x)) -} - -pub fn read_buffer_with_borrow(mut cx: FunctionContext) -> JsResult { - let buf = cx.argument::(0)?; - let i = cx.argument::(1)?.value(&mut cx) as usize; - let n = buf.as_slice(&cx)[i]; - - Ok(cx.number(n as f64)) -} - -pub fn write_buffer_with_lock(mut cx: FunctionContext) -> JsResult { - let mut b: Handle = cx.argument(0)?; - let i = cx.argument::(1)?.value(&mut cx) as usize; - let x = cx.argument::(2)?.value(&mut cx) as u8; - let lock = cx.lock(); - - b.try_borrow_mut(&lock) - .map(|mut slice| slice[i] = x) - .or_throw(&mut cx)?; - - Ok(cx.undefined()) -} - -pub fn write_buffer_with_borrow_mut(mut cx: FunctionContext) -> JsResult { - let mut buf = cx.argument::(0)?; - let i = cx.argument::(1)?.value(&mut cx) as usize; - let n = cx.argument::(2)?.value(&mut cx) as u8; - - buf.as_mut_slice(&mut cx)[i] = n; - - Ok(cx.undefined()) -} - // Accepts either a `JsString` or `JsBuffer` and returns the contents as // as bytes; avoids copying. fn get_bytes<'cx, 'a, C>(cx: &'a mut C, v: Handle) -> NeonResult> diff --git a/test/napi/src/js/typedarrays.rs b/test/napi/src/js/typedarrays.rs new file mode 100644 index 000000000..6a5cf8766 --- /dev/null +++ b/test/napi/src/js/typedarrays.rs @@ -0,0 +1,359 @@ +use neon::{ + prelude::*, + types::buffer::{Binary, BorrowError, TypedArray}, +}; + +pub fn return_array_buffer(mut cx: FunctionContext) -> JsResult { + let b: Handle = cx.array_buffer(16)?; + Ok(b) +} + +pub fn read_array_buffer_with_lock(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::>(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let lock = cx.lock(); + let n = buf.try_borrow(&lock).map(|buf| buf[i]).or_throw(&mut cx)?; + + Ok(cx.number(n)) +} + +pub fn read_array_buffer_with_borrow(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let n = buf.as_slice(&cx)[i]; + + Ok(cx.number(n as f64)) +} + +pub fn write_array_buffer_with_lock(mut cx: FunctionContext) -> JsResult { + let mut b: Handle = cx.argument(0)?; + let i = cx.argument::(1)?.value(&mut cx) as u32 as usize; + let x = cx.argument::(2)?.value(&mut cx) as u8; + let lock = cx.lock(); + + b.try_borrow_mut(&lock) + .map(|mut slice| { + slice[i] = x; + }) + .or_throw(&mut cx)?; + + Ok(cx.undefined()) +} + +pub fn write_array_buffer_with_borrow_mut(mut cx: FunctionContext) -> JsResult { + let mut buf = cx.argument::(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let n = cx.argument::(2)?.value(&mut cx) as u8; + + buf.as_mut_slice(&mut cx)[i] = n; + + Ok(cx.undefined()) +} + +pub fn read_typed_array_with_borrow(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::>(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let n = buf.as_slice(&cx)[i]; + + Ok(cx.number(n as f64)) +} + +pub fn write_typed_array_with_borrow_mut(mut cx: FunctionContext) -> JsResult { + let mut buf = cx.argument::>(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let n = cx.argument::(2)?.value(&mut cx) as i32; + + buf.as_mut_slice(&mut cx)[i] = n; + + Ok(cx.undefined()) +} + +pub fn read_u8_typed_array(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::>(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let n = buf.as_slice(&cx)[i]; + + Ok(cx.number(n as f64)) +} + +pub fn copy_typed_array(mut cx: FunctionContext) -> JsResult { + let source = cx.argument::>(0)?; + let mut dest = cx.argument::>(1)?; + let mut run = || -> Result<_, BorrowError> { + let lock = cx.lock(); + let source = source.try_borrow(&lock)?; + let mut dest = dest.try_borrow_mut(&lock)?; + + dest.copy_from_slice(&source); + + Ok(()) + }; + + run().or_throw(&mut cx)?; + + Ok(cx.undefined()) +} + +pub fn return_uninitialized_buffer(mut cx: FunctionContext) -> JsResult { + let b: Handle = unsafe { JsBuffer::uninitialized(&mut cx, 16)? }; + Ok(b) +} + +pub fn return_buffer(mut cx: FunctionContext) -> JsResult { + let b: Handle = cx.buffer(16)?; + Ok(b) +} + +pub fn return_external_buffer(mut cx: FunctionContext) -> JsResult { + let data = cx.argument::(0)?.value(&mut cx); + let buf = JsBuffer::external(&mut cx, data.into_bytes()); + + Ok(buf) +} + +pub fn return_external_array_buffer(mut cx: FunctionContext) -> JsResult { + let data = cx.argument::(0)?.value(&mut cx); + let buf = JsArrayBuffer::external(&mut cx, data.into_bytes()); + + Ok(buf) +} + +pub fn return_int8array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + JsInt8Array::from_buffer(&mut cx, buf) +} + +pub fn return_int16array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + JsInt16Array::from_buffer(&mut cx, buf) +} + +pub fn return_uint32array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + JsUint32Array::from_buffer(&mut cx, buf) +} + +pub fn return_float64array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + JsFloat64Array::from_buffer(&mut cx, buf) +} + +pub fn return_biguint64array_from_arraybuffer( + mut cx: FunctionContext, +) -> JsResult { + let buf = cx.argument::(0)?; + JsBigUint64Array::from_buffer(&mut cx, buf) +} + +pub fn return_new_int32array(mut cx: FunctionContext) -> JsResult { + let len = cx.argument::(0)?.value(&mut cx) as usize; + JsInt32Array::new(&mut cx, len) +} + +pub fn return_uint32array_from_arraybuffer_region( + mut cx: FunctionContext, +) -> JsResult { + let buf = cx.argument::(0)?; + let byte_offset = cx.argument::(1)?; + let byte_offset = byte_offset.value(&mut cx); + let len = cx.argument::(2)?; + let len = len.value(&mut cx); + JsUint32Array::from_buffer_region(&mut cx, buf, byte_offset as usize, len as usize) +} + +pub fn get_arraybuffer_byte_length(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + let byte_length = buf.byte_length(&mut cx); + let n = cx.number(byte_length as u32); + Ok(n) +} + +fn typed_array_info<'cx, C, T: Binary>( + cx: &mut C, + a: Handle<'cx, JsTypedArray>, +) -> JsResult<'cx, JsObject> +where + C: Context<'cx>, +{ + let byte_offset = a.byte_offset(cx); + let byte_offset = cx.number(byte_offset as u32); + + let len = a.len(cx); + let len = cx.number(len as u32); + + let byte_length = a.byte_length(cx); + let byte_length = cx.number(byte_length as u32); + + let buffer = a.buffer(cx); + + let obj = cx.empty_object(); + + obj.set(cx, "byteOffset", byte_offset)?; + obj.set(cx, "length", len)?; + obj.set(cx, "byteLength", byte_length)?; + obj.set(cx, "buffer", buffer)?; + + Ok(obj) +} + +pub fn detach_same_handle(mut cx: FunctionContext) -> JsResult { + let a = cx.argument::(0)?; + let detach = cx.argument::(1)?; + + let before = typed_array_info(&mut cx, a)?; + detach.call_with(&cx) + .arg(a) + .exec(&mut cx)?; + let after = typed_array_info(&mut cx, a)?; + + let result = cx.empty_object(); + + result.set(&mut cx, "before", before)?; + result.set(&mut cx, "after", after)?; + + Ok(result) +} + +pub fn detach_and_escape(mut cx: FunctionContext) -> JsResult { + static EXPANDO_KEY: &str = "__neon_test__:before"; + + let detach = cx.argument::(0)?; + + let a = cx.compute_scoped(|mut cx| { + let buf = cx.array_buffer(16)?; + let a = JsUint32Array::from_buffer(&mut cx, buf)?; + let before = typed_array_info(&mut cx, a)?; + a.set(&mut cx, EXPANDO_KEY, before)?; + detach.call_with(&cx) + .arg(a) + .exec(&mut cx)?; + Ok(a) + })?; + + let before = a.get::(&mut cx, EXPANDO_KEY)?; + let after = typed_array_info(&mut cx, a)?; + + let result = cx.empty_object(); + + result.set(&mut cx, "before", before)?; + result.set(&mut cx, "after", after)?; + + Ok(result) +} + +pub fn detach_and_cast(mut cx: FunctionContext) -> JsResult { + let a = cx.argument::(0)?; + let detach = cx.argument::(1)?; + + let before = typed_array_info(&mut cx, a)?; + + detach.call_with(&cx) + .arg(a) + .exec(&mut cx)?; + + let v = a.upcast::(); + let a = v.downcast_or_throw::(&mut cx)?; + + let after = typed_array_info(&mut cx, a)?; + + let result = cx.empty_object(); + + result.set(&mut cx, "before", before)?; + result.set(&mut cx, "after", after)?; + + Ok(result) +} + +pub fn detach_and_unroot(mut cx: FunctionContext) -> JsResult { + let a = cx.argument::(0)?; + let detach = cx.argument::(1)?; + + let before = typed_array_info(&mut cx, a)?; + + detach.call_with(&cx) + .arg(a) + .exec(&mut cx)?; + + let root = a.root(&mut cx); + let a = root.into_inner(&mut cx); + + let after = typed_array_info(&mut cx, a)?; + + let result = cx.empty_object(); + + result.set(&mut cx, "before", before)?; + result.set(&mut cx, "after", after)?; + + Ok(result) +} + +pub fn get_typed_array_info(mut cx: FunctionContext) -> JsResult { + let x = cx.argument::(0)?; + + if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else { + cx.throw_type_error("expected a typed array") + } +} + +pub fn read_buffer_with_lock(mut cx: FunctionContext) -> JsResult { + let b: Handle = cx.argument(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let lock = cx.lock(); + let x = b + .try_borrow(&lock) + .map(|slice| slice[i]) + .or_throw(&mut cx)?; + + Ok(cx.number(x)) +} + +pub fn read_buffer_with_borrow(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let n = buf.as_slice(&cx)[i]; + + Ok(cx.number(n as f64)) +} + +pub fn write_buffer_with_lock(mut cx: FunctionContext) -> JsResult { + let mut b: Handle = cx.argument(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let x = cx.argument::(2)?.value(&mut cx) as u8; + let lock = cx.lock(); + + b.try_borrow_mut(&lock) + .map(|mut slice| slice[i] = x) + .or_throw(&mut cx)?; + + Ok(cx.undefined()) +} + +pub fn write_buffer_with_borrow_mut(mut cx: FunctionContext) -> JsResult { + let mut buf = cx.argument::(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let n = cx.argument::(2)?.value(&mut cx) as u8; + + buf.as_mut_slice(&mut cx)[i] = n; + + Ok(cx.undefined()) +} diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index 4c1653286..8b7e2b409 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -2,7 +2,7 @@ use neon::prelude::*; use crate::js::{ arrays::*, boxed::*, coercions::*, date::*, errors::*, functions::*, numbers::*, objects::*, - strings::*, threads::*, types::*, + strings::*, threads::*, typedarrays::*, types::*, }; mod js { @@ -17,6 +17,7 @@ mod js { pub mod objects; pub mod strings; pub mod threads; + pub mod typedarrays; pub mod types; pub mod workers; } @@ -257,6 +258,11 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { "return_uint32array_from_arraybuffer_region", return_uint32array_from_arraybuffer_region, )?; + cx.export_function("get_arraybuffer_byte_length", get_arraybuffer_byte_length)?; + cx.export_function("detach_same_handle", detach_same_handle)?; + cx.export_function("detach_and_escape", detach_and_escape)?; + cx.export_function("detach_and_cast", detach_and_cast)?; + cx.export_function("detach_and_unroot", detach_and_unroot)?; cx.export_function("get_typed_array_info", get_typed_array_info)?; cx.export_function("read_buffer_with_lock", read_buffer_with_lock)?; cx.export_function("read_buffer_with_borrow", read_buffer_with_borrow)?; From ffb089bd5d3a64991ef8a728c644f88e1ac61948 Mon Sep 17 00:00:00 2001 From: David Herman Date: Fri, 22 Jul 2022 15:56:00 -0700 Subject: [PATCH 19/31] Abstract some boilerplate in the various detaching test cases --- test/napi/lib/typedarrays.js | 55 +++++++----------- test/napi/src/js/typedarrays.rs | 100 +++++++++++--------------------- 2 files changed, 55 insertions(+), 100 deletions(-) diff --git a/test/napi/lib/typedarrays.js b/test/napi/lib/typedarrays.js index c9ae40348..692010ef9 100644 --- a/test/napi/lib/typedarrays.js +++ b/test/napi/lib/typedarrays.js @@ -421,60 +421,49 @@ describe("Typed arrays", function () { assert.strictEqual(addon.get_arraybuffer_byte_length(buf), 0); }); + function testDetach(arr, addonFn, byteLengthBefore, lengthBefore) { + let { before, after } = addonFn(arr, (arr) => detach(arr.buffer)); + + assert.strictEqual(before.byteLength, byteLengthBefore); + assert.strictEqual(before.length, lengthBefore); + assert.strictEqual(after.byteLength, 0); + assert.strictEqual(after.length, 0); + } + it("provides correct metadata when detaching a typed array's buffer", function () { - var buf = new ArrayBuffer(16); - var arr = new Uint32Array(buf); + var arr = new Uint32Array(4); + var buf = arr.buffer; assert.strictEqual(buf.byteLength, 16); assert.strictEqual(arr.byteLength, 16); assert.strictEqual(arr.length, 4); - assert.strictEqual(addon.get_typed_array_info(arr).byteLength, 16); - assert.strictEqual(addon.get_typed_array_info(arr).length, 4); - assert.strictEqual(addon.get_typed_array_info(arr).buffer, buf); - let { before, after } = addon.detach_same_handle(arr, (arr) => detach(arr.buffer)); + var info = addon.get_typed_array_info(arr); - assert.strictEqual(before.byteLength, 16); - assert.strictEqual(before.length, 4); + assert.strictEqual(info.byteLength, 16); + assert.strictEqual(info.length, 4); + assert.strictEqual(info.buffer, buf); + + testDetach(arr, addon.detach_same_handle, 16, 4); + + var info = addon.get_typed_array_info(arr); assert.strictEqual(arr.byteLength, 0); assert.strictEqual(arr.length, 0); assert.strictEqual(addon.get_typed_array_info(arr).byteLength, 0); assert.strictEqual(addon.get_typed_array_info(arr).length, 0); - assert.strictEqual(after.byteLength, 0); - assert.strictEqual(after.length, 0); }); it("provides correct metadata when detaching an escaped typed array's buffer", function () { - let { before, after } = addon.detach_and_escape((arr) => detach(arr.buffer)); - assert.strictEqual(before.byteLength, 16); - assert.strictEqual(before.length, 4); - assert.strictEqual(after.byteLength, 0); - assert.strictEqual(after.length, 0); + testDetach(new Uint32Array(4), addon.detach_and_escape, 16, 4); }); it("provides correct metadata when detaching a casted typed array's buffer", function () { - var buf = new ArrayBuffer(16); - var arr = new Uint32Array(buf); - - let { before, after } = addon.detach_and_cast(arr, (arr) => detach(arr.buffer)); - - assert.strictEqual(before.byteLength, 16); - assert.strictEqual(before.length, 4); - assert.strictEqual(after.byteLength, 0); - assert.strictEqual(after.length, 0); + testDetach(new Uint32Array(4), addon.detach_and_cast, 16, 4); }); it("provides correct metadata when detaching an un-rooted typed array's buffer", function () { - var buf = new ArrayBuffer(16); - var arr = new Uint32Array(buf); - - let { before, after } = addon.detach_and_unroot(arr, (arr) => detach(arr.buffer)); - - assert.strictEqual(before.byteLength, 16); - assert.strictEqual(before.length, 4); - assert.strictEqual(after.byteLength, 0); - assert.strictEqual(after.length, 0); + testDetach(new Uint32Array(4), addon.detach_and_unroot, 16, 4); }); }); diff --git a/test/napi/src/js/typedarrays.rs b/test/napi/src/js/typedarrays.rs index 6a5cf8766..deafd9464 100644 --- a/test/napi/src/js/typedarrays.rs +++ b/test/napi/src/js/typedarrays.rs @@ -196,53 +196,14 @@ where Ok(obj) } -pub fn detach_same_handle(mut cx: FunctionContext) -> JsResult { - let a = cx.argument::(0)?; - let detach = cx.argument::(1)?; - - let before = typed_array_info(&mut cx, a)?; - detach.call_with(&cx) - .arg(a) - .exec(&mut cx)?; - let after = typed_array_info(&mut cx, a)?; - - let result = cx.empty_object(); - - result.set(&mut cx, "before", before)?; - result.set(&mut cx, "after", after)?; - - Ok(result) -} - -pub fn detach_and_escape(mut cx: FunctionContext) -> JsResult { - static EXPANDO_KEY: &str = "__neon_test__:before"; - - let detach = cx.argument::(0)?; - - let a = cx.compute_scoped(|mut cx| { - let buf = cx.array_buffer(16)?; - let a = JsUint32Array::from_buffer(&mut cx, buf)?; - let before = typed_array_info(&mut cx, a)?; - a.set(&mut cx, EXPANDO_KEY, before)?; - detach.call_with(&cx) - .arg(a) - .exec(&mut cx)?; - Ok(a) - })?; - - let before = a.get::(&mut cx, EXPANDO_KEY)?; - let after = typed_array_info(&mut cx, a)?; - - let result = cx.empty_object(); - - result.set(&mut cx, "before", before)?; - result.set(&mut cx, "after", after)?; - - Ok(result) -} - -pub fn detach_and_cast(mut cx: FunctionContext) -> JsResult { - let a = cx.argument::(0)?; +fn detach_and_then<'cx, F>(mut cx: FunctionContext<'cx>, f: F) -> JsResult +where + F: FnOnce( + &mut FunctionContext<'cx>, + Handle<'cx, JsUint32Array>, + ) -> NeonResult>> +{ + let mut a = cx.argument::(0)?; let detach = cx.argument::(1)?; let before = typed_array_info(&mut cx, a)?; @@ -251,8 +212,9 @@ pub fn detach_and_cast(mut cx: FunctionContext) -> JsResult { .arg(a) .exec(&mut cx)?; - let v = a.upcast::(); - let a = v.downcast_or_throw::(&mut cx)?; + if let Some(new_array) = f(&mut cx, a)? { + a = new_array; + } let after = typed_array_info(&mut cx, a)?; @@ -264,27 +226,31 @@ pub fn detach_and_cast(mut cx: FunctionContext) -> JsResult { Ok(result) } -pub fn detach_and_unroot(mut cx: FunctionContext) -> JsResult { - let a = cx.argument::(0)?; - let detach = cx.argument::(1)?; - - let before = typed_array_info(&mut cx, a)?; - - detach.call_with(&cx) - .arg(a) - .exec(&mut cx)?; - - let root = a.root(&mut cx); - let a = root.into_inner(&mut cx); - - let after = typed_array_info(&mut cx, a)?; +pub fn detach_same_handle(cx: FunctionContext) -> JsResult { + detach_and_then(cx, |_, _| { Ok(None) }) +} - let result = cx.empty_object(); +pub fn detach_and_escape(cx: FunctionContext) -> JsResult { + detach_and_then(cx, |cx, a| { + let a = cx.compute_scoped(|_| { Ok(a) })?; + Ok(Some(a)) + }) +} - result.set(&mut cx, "before", before)?; - result.set(&mut cx, "after", after)?; +pub fn detach_and_cast(cx: FunctionContext) -> JsResult { + detach_and_then(cx, |cx, a| { + let v = a.upcast::(); + let a = v.downcast_or_throw::(cx)?; + Ok(Some(a)) + }) +} - Ok(result) +pub fn detach_and_unroot(cx: FunctionContext) -> JsResult { + detach_and_then(cx, |cx, a| { + let root = a.root(cx); + let a = root.into_inner(cx); + Ok(Some(a)) + }) } pub fn get_typed_array_info(mut cx: FunctionContext) -> JsResult { From 480298c15aa81610a7a28ba3fd6967252246e5fd Mon Sep 17 00:00:00 2001 From: David Herman Date: Fri, 22 Jul 2022 17:32:35 -0700 Subject: [PATCH 20/31] Test byteOffset as well --- test/napi/lib/typedarrays.js | 39 +++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/test/napi/lib/typedarrays.js b/test/napi/lib/typedarrays.js index 692010ef9..4434a68bc 100644 --- a/test/napi/lib/typedarrays.js +++ b/test/napi/lib/typedarrays.js @@ -421,49 +421,64 @@ describe("Typed arrays", function () { assert.strictEqual(addon.get_arraybuffer_byte_length(buf), 0); }); - function testDetach(arr, addonFn, byteLengthBefore, lengthBefore) { + function testDetach(arr, addonFn, byteLengthBefore, lengthBefore, byteOffsetBefore) { let { before, after } = addonFn(arr, (arr) => detach(arr.buffer)); assert.strictEqual(before.byteLength, byteLengthBefore); assert.strictEqual(before.length, lengthBefore); + assert.strictEqual(before.byteOffset, byteOffsetBefore); assert.strictEqual(after.byteLength, 0); assert.strictEqual(after.length, 0); + assert.strictEqual(after.byteOffset, 0); } it("provides correct metadata when detaching a typed array's buffer", function () { - var arr = new Uint32Array(4); + var buf = new ArrayBuffer(16); + var arr = new Uint32Array(buf, 4, 2); var buf = arr.buffer; assert.strictEqual(buf.byteLength, 16); - assert.strictEqual(arr.byteLength, 16); - assert.strictEqual(arr.length, 4); + + assert.strictEqual(arr.byteLength, 8); + assert.strictEqual(arr.length, 2); + assert.strictEqual(arr.byteOffset, 4); var info = addon.get_typed_array_info(arr); - assert.strictEqual(info.byteLength, 16); - assert.strictEqual(info.length, 4); + assert.strictEqual(info.byteLength, 8); + assert.strictEqual(info.length, 2); + assert.strictEqual(info.byteOffset, 4); assert.strictEqual(info.buffer, buf); - testDetach(arr, addon.detach_same_handle, 16, 4); + testDetach(arr, addon.detach_same_handle, 8, 2, 4); var info = addon.get_typed_array_info(arr); + assert.strictEqual(buf.byteLength, 0); + + assert.strictEqual(arr.buffer, buf); assert.strictEqual(arr.byteLength, 0); assert.strictEqual(arr.length, 0); - assert.strictEqual(addon.get_typed_array_info(arr).byteLength, 0); - assert.strictEqual(addon.get_typed_array_info(arr).length, 0); + assert.strictEqual(arr.byteOffset, 0); + assert.strictEqual(info.byteLength, 0); + assert.strictEqual(info.length, 0); + assert.strictEqual(info.byteOffset, 0); + assert.strictEqual(info.buffer, buf); }); it("provides correct metadata when detaching an escaped typed array's buffer", function () { - testDetach(new Uint32Array(4), addon.detach_and_escape, 16, 4); + var buf = new ArrayBuffer(16); + testDetach(new Uint32Array(buf, 4, 2), addon.detach_and_escape, 8, 2, 4); }); it("provides correct metadata when detaching a casted typed array's buffer", function () { - testDetach(new Uint32Array(4), addon.detach_and_cast, 16, 4); + var buf = new ArrayBuffer(16); + testDetach(new Uint32Array(buf, 4, 2), addon.detach_and_cast, 8, 2, 4); }); it("provides correct metadata when detaching an un-rooted typed array's buffer", function () { - testDetach(new Uint32Array(4), addon.detach_and_unroot, 16, 4); + var buf = new ArrayBuffer(16); + testDetach(new Uint32Array(buf, 4, 2), addon.detach_and_unroot, 8, 2, 4); }); }); From 5785b96436b4780f77862f14a4ae7555503bf862 Mon Sep 17 00:00:00 2001 From: David Herman Date: Fri, 22 Jul 2022 17:44:29 -0700 Subject: [PATCH 21/31] Fix the tests by removing the caching of offset and length --- crates/neon/src/types_impl/buffer/mod.rs | 2 -- crates/neon/src/types_impl/buffer/types.rs | 14 ++++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/neon/src/types_impl/buffer/mod.rs b/crates/neon/src/types_impl/buffer/mod.rs index 19c20b155..f51729971 100644 --- a/crates/neon/src/types_impl/buffer/mod.rs +++ b/crates/neon/src/types_impl/buffer/mod.rs @@ -192,8 +192,6 @@ mod private { pub struct JsTypedArrayInner { pub(super) local: raw::Local, pub(super) buffer: raw::Local, - pub(super) byte_offset: usize, - pub(super) len: usize, pub(super) _type: PhantomData, } } diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index 7ac4a88d7..d15415bc4 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -420,8 +420,6 @@ impl Managed for JsTypedArray { Self(JsTypedArrayInner { local, buffer: info.buf, - byte_offset: info.offset, - len: info.length, _type: PhantomData, }) } @@ -551,8 +549,6 @@ impl JsTypedArray { Ok(Handle::new_internal(Self(JsTypedArrayInner { local: arr, buffer: buffer.to_raw(), - byte_offset, - len, _type: PhantomData, }))) } else { @@ -588,11 +584,12 @@ impl JsTypedArray { /// Returns the offset (in bytes) of the typed array from the start of its /// [`JsArrayBuffer`](JsArrayBuffer). - pub fn byte_offset<'cx, C>(&self, _cx: &mut C) -> usize + pub fn byte_offset<'cx, C>(&self, cx: &mut C) -> usize where C: Context<'cx>, { - self.0.byte_offset + let info = unsafe { sys::typedarray::info(cx.env().to_raw(), self.to_raw()) }; + info.offset } /// Returns the length of the typed array, i.e. the number of elements. @@ -604,11 +601,12 @@ impl JsTypedArray { /// self.byte_length() == self.len() * size_of::() /// ``` #[allow(clippy::len_without_is_empty)] - pub fn len<'cx, C>(&self, _cx: &mut C) -> usize + pub fn len<'cx, C>(&self, cx: &mut C) -> usize where C: Context<'cx>, { - self.0.len + let info = unsafe { sys::typedarray::info(cx.env().to_raw(), self.to_raw()) }; + info.length } } From 6bc6aaa672755b52ebde2fd213e04b2023aec6ff Mon Sep 17 00:00:00 2001 From: David Herman Date: Sat, 23 Jul 2022 15:02:02 -0700 Subject: [PATCH 22/31] Add a `Region` type to represent a typed region of a buffer: - Shorten `from_buffer_region` to `from_region` - Add symmetric `to_region` method, which only requires a single `napi_get_typed_array_info` FFI call --- crates/neon/src/types_impl/buffer/mod.rs | 80 +++++++++++++++++++++- crates/neon/src/types_impl/buffer/types.rs | 64 +++++++++++++---- test/napi/src/js/typedarrays.rs | 2 +- 3 files changed, 129 insertions(+), 17 deletions(-) diff --git a/crates/neon/src/types_impl/buffer/mod.rs b/crates/neon/src/types_impl/buffer/mod.rs index f51729971..ca82b2bb2 100644 --- a/crates/neon/src/types_impl/buffer/mod.rs +++ b/crates/neon/src/types_impl/buffer/mod.rs @@ -4,13 +4,15 @@ use std::{ cell::RefCell, error::Error, fmt::{self, Debug, Display}, + marker::PhantomData, ops::{Deref, DerefMut}, }; use crate::{ context::Context, - result::{NeonResult, ResultExt}, - types::buffer::lock::{Ledger, Lock}, + handle::Handle, + result::{JsResult, NeonResult, ResultExt}, + types::{buffer::lock::{Ledger, Lock}, JsArrayBuffer, JsTypedArray}, }; pub(crate) mod lock; @@ -181,6 +183,80 @@ impl ResultExt for Result { } } +/// Represents a typed region of an [`ArrayBuffer`](crate::types::JsArrayBuffer). +/// +/// A `Region` can be created via the +/// [`Handle::region()`](crate::handle::Handle::region) or +/// [`JsTypedArray::to_region()`](crate::types::JsTypedArray::to_region) methods. +/// +/// A region is **not** checked for validity until it is converted +/// a typed array via [`to_typed_array()`](Region::to_typed_array) or +/// [`JsTypedArray::from_region()`](crate::types::JsTypedArray::from_region). +/// +/// # Example +/// +/// ``` +/// # use crate::prelude::*; +/// # fn f(mut cx: FunctionContext) -> JsResult { +/// // Allocate a 16-byte ArrayBuffer and a uint32 array of length 2 (i.e., 8 bytes) +/// // starting at byte offset 4 of the buffer: +/// // +/// // 0 4 8 12 16 +/// // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// // buf: | | | | | | | | | | | | | | | | | +/// // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// // ^ ^ +/// // | | +/// // +-------+-------+ +/// // arr: | | | +/// // +-------+-------+ +/// // 0 1 2 +/// let buf = cx.array_buffer(16); +/// let arr = JsUint32Array::from_region(&mut cx, buf.region(4, 2))?; +/// # Ok(arr) +/// # } +/// ``` +#[derive(Clone,Copy)] +pub struct Region<'cx, T: Binary> { + pub(super) buffer: Handle<'cx, JsArrayBuffer>, + pub(super) byte_offset: usize, + pub(super) len: usize, + pub(super) phantom: PhantomData, +} + +impl<'cx, T: Binary> Region<'cx, T> { + /// Returns the handle to the region's buffer. + pub fn buffer(self) -> Handle<'cx, JsArrayBuffer> { self.buffer } + + /// Returns the starting byte offset of the region. + pub fn byte_offset(self) -> usize { self.byte_offset } + + /// Returns the number of elements of type `T` in the region. + pub fn len(self) -> usize { self.len } + + /// Returns the byte length of the region, which is equal to + /// `(self.len() * size_of::())`. + pub fn byte_length(self) -> usize { self.len * std::mem::size_of::() } + + /// Constructs a typed array for this buffer region. + /// + /// The resulting typed array has `self.len()` elements and byte length + /// `self.byte_length()`. + /// + /// Throws an exception if the region is invalid, for example if the starting + /// offset is not properly aligned, or the length goes beyond the end of the + /// buffer. + pub fn to_typed_array<'c, C>( + self, + cx: &mut C, + ) -> JsResult<'c, JsTypedArray> + where + C: Context<'c>, + { + JsTypedArray::from_region(cx, self) + } +} + mod private { use super::Binary; use crate::sys::raw; diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index d15415bc4..29d82413a 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -9,7 +9,7 @@ use crate::{ types::buffer::{ lock::{Ledger, Lock}, private::{self, JsTypedArrayInner}, - BorrowError, Ref, RefMut, TypedArray, + BorrowError, Ref, RefMut, Region, TypedArray, }, types::{private::ValueInternal, Value}, }; @@ -210,6 +210,20 @@ impl JsArrayBuffer { } } +impl<'cx> Handle<'cx, JsArrayBuffer> { + /// Returns a [`Region`](crate::types::buffer::Region) representing a typed + /// region of this buffer, starting at `byte_offset` and containing `len` + /// elements of type `T`. + /// + /// The region is **not** checked for validity by this method. Regions are only + /// validated when they are converted to typed arrays. + /// + /// See the [`Region`](Region) documentation for more information. + pub fn region(self, byte_offset: usize, len: usize) -> Region<'cx, T> { + Region { buffer: self, byte_offset, len, phantom: PhantomData } + } +} + unsafe impl TransparentNoCopyWrapper for JsArrayBuffer { type Inner = raw::Local; @@ -503,7 +517,10 @@ impl JsTypedArray { /// Constructs a typed array that views `buffer`. /// /// The resulting typed array has `(buffer.byte_length() / size_of::())` elements. - pub fn from_buffer<'cx, C>(cx: &mut C, buffer: Handle) -> JsResult<'cx, Self> + pub fn from_buffer<'cx, 'b: 'cx, C>( + cx: &mut C, + buffer: Handle<'b, JsArrayBuffer>, + ) -> JsResult<'cx, Self> where C: Context<'cx>, { @@ -518,23 +535,26 @@ impl JsTypedArray { ); } - Self::from_buffer_region(cx, buffer, 0, len) + Self::from_region(cx, buffer.region(0, len)) } - /// Constructs a typed array that views a region of `buffer` starting - /// at the specified byte offset and with the specified number of elements. + /// Constructs a typed array for the specified buffer region. /// - /// The resulting typed array has `len` elements and byte length - /// `(len * size_of::())`. - pub fn from_buffer_region<'cx, C>( + /// The resulting typed array has `region.len()` elements and byte length + /// `region.byte_length()`. + /// + /// Throws an exception if the region is invalid, for example if the starting + /// offset is not properly aligned, or the length goes beyond the end of the + /// buffer. + pub fn from_region<'c, 'r, C>( cx: &mut C, - buffer: Handle, - byte_offset: usize, - len: usize, - ) -> JsResult<'cx, Self> + region: Region<'r, T>, + ) -> JsResult<'c, Self> where - C: Context<'cx>, + C: Context<'c>, { + let Region { buffer, byte_offset, len, .. } = region; + let result = unsafe { sys::typedarray::new( cx.env().to_raw(), @@ -556,6 +576,22 @@ impl JsTypedArray { } } + /// Returns information about the backing buffer region for this typed array. + pub fn to_region<'cx, C>(&self, cx: &mut C) -> Region<'cx, T> + where + C: Context<'cx>, + { + let env = cx.env(); + let info = unsafe { sys::typedarray::info(env.to_raw(), self.to_raw()) }; + + Region { + buffer: Handle::new_internal(JsArrayBuffer::from_raw(cx.env(), info.buf)), + byte_offset: info.offset, + len: info.length, + phantom: PhantomData, + } + } + /// Constructs a new typed array of length `len`. /// /// The resulting typed array has a newly allocated storage buffer of @@ -565,7 +601,7 @@ impl JsTypedArray { C: Context<'cx>, { let buffer = cx.array_buffer(len * std::mem::size_of::())?; - Self::from_buffer_region(cx, buffer, 0, len) + Self::from_region(cx, buffer.region(0, len)) } /// Returns the [`JsArrayBuffer`](JsArrayBuffer) that owns the underlying storage buffer diff --git a/test/napi/src/js/typedarrays.rs b/test/napi/src/js/typedarrays.rs index deafd9464..257731da1 100644 --- a/test/napi/src/js/typedarrays.rs +++ b/test/napi/src/js/typedarrays.rs @@ -158,7 +158,7 @@ pub fn return_uint32array_from_arraybuffer_region( let byte_offset = byte_offset.value(&mut cx); let len = cx.argument::(2)?; let len = len.value(&mut cx); - JsUint32Array::from_buffer_region(&mut cx, buf, byte_offset as usize, len as usize) + JsUint32Array::from_region(&mut cx, buf.region(byte_offset as usize, len as usize)) } pub fn get_arraybuffer_byte_length(mut cx: FunctionContext) -> JsResult { From 9cf8c53fa1b369cd2751b507f6fe09290eefaa63 Mon Sep 17 00:00:00 2001 From: David Herman Date: Sat, 23 Jul 2022 15:07:16 -0700 Subject: [PATCH 23/31] rustfmt --- crates/neon/src/types_impl/buffer/mod.rs | 28 ++++++++++++++-------- crates/neon/src/types_impl/buffer/types.rs | 19 ++++++++++----- test/napi/src/js/objects.rs | 5 +--- test/napi/src/js/typedarrays.rs | 10 ++++---- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/crates/neon/src/types_impl/buffer/mod.rs b/crates/neon/src/types_impl/buffer/mod.rs index ca82b2bb2..e4121be25 100644 --- a/crates/neon/src/types_impl/buffer/mod.rs +++ b/crates/neon/src/types_impl/buffer/mod.rs @@ -12,7 +12,10 @@ use crate::{ context::Context, handle::Handle, result::{JsResult, NeonResult, ResultExt}, - types::{buffer::lock::{Ledger, Lock}, JsArrayBuffer, JsTypedArray}, + types::{ + buffer::lock::{Ledger, Lock}, + JsArrayBuffer, JsTypedArray, + }, }; pub(crate) mod lock; @@ -216,7 +219,7 @@ impl ResultExt for Result { /// # Ok(arr) /// # } /// ``` -#[derive(Clone,Copy)] +#[derive(Clone, Copy)] pub struct Region<'cx, T: Binary> { pub(super) buffer: Handle<'cx, JsArrayBuffer>, pub(super) byte_offset: usize, @@ -226,17 +229,25 @@ pub struct Region<'cx, T: Binary> { impl<'cx, T: Binary> Region<'cx, T> { /// Returns the handle to the region's buffer. - pub fn buffer(self) -> Handle<'cx, JsArrayBuffer> { self.buffer } + pub fn buffer(self) -> Handle<'cx, JsArrayBuffer> { + self.buffer + } /// Returns the starting byte offset of the region. - pub fn byte_offset(self) -> usize { self.byte_offset } + pub fn byte_offset(self) -> usize { + self.byte_offset + } /// Returns the number of elements of type `T` in the region. - pub fn len(self) -> usize { self.len } + pub fn len(self) -> usize { + self.len + } /// Returns the byte length of the region, which is equal to /// `(self.len() * size_of::())`. - pub fn byte_length(self) -> usize { self.len * std::mem::size_of::() } + pub fn byte_length(self) -> usize { + self.len * std::mem::size_of::() + } /// Constructs a typed array for this buffer region. /// @@ -246,10 +257,7 @@ impl<'cx, T: Binary> Region<'cx, T> { /// Throws an exception if the region is invalid, for example if the starting /// offset is not properly aligned, or the length goes beyond the end of the /// buffer. - pub fn to_typed_array<'c, C>( - self, - cx: &mut C, - ) -> JsResult<'c, JsTypedArray> + pub fn to_typed_array<'c, C>(self, cx: &mut C) -> JsResult<'c, JsTypedArray> where C: Context<'c>, { diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index 29d82413a..b503bb480 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -220,7 +220,12 @@ impl<'cx> Handle<'cx, JsArrayBuffer> { /// /// See the [`Region`](Region) documentation for more information. pub fn region(self, byte_offset: usize, len: usize) -> Region<'cx, T> { - Region { buffer: self, byte_offset, len, phantom: PhantomData } + Region { + buffer: self, + byte_offset, + len, + phantom: PhantomData, + } } } @@ -546,14 +551,16 @@ impl JsTypedArray { /// Throws an exception if the region is invalid, for example if the starting /// offset is not properly aligned, or the length goes beyond the end of the /// buffer. - pub fn from_region<'c, 'r, C>( - cx: &mut C, - region: Region<'r, T>, - ) -> JsResult<'c, Self> + pub fn from_region<'c, 'r, C>(cx: &mut C, region: Region<'r, T>) -> JsResult<'c, Self> where C: Context<'c>, { - let Region { buffer, byte_offset, len, .. } = region; + let Region { + buffer, + byte_offset, + len, + .. + } = region; let result = unsafe { sys::typedarray::new( diff --git a/test/napi/src/js/objects.rs b/test/napi/src/js/objects.rs index f5af8f8fb..4c4f7f582 100644 --- a/test/napi/src/js/objects.rs +++ b/test/napi/src/js/objects.rs @@ -1,9 +1,6 @@ use std::borrow::Cow; -use neon::{ - prelude::*, - types::buffer::TypedArray, -}; +use neon::{prelude::*, types::buffer::TypedArray}; pub fn return_js_global_object(mut cx: FunctionContext) -> JsResult { Ok(cx.global()) diff --git a/test/napi/src/js/typedarrays.rs b/test/napi/src/js/typedarrays.rs index 257731da1..1573d8b05 100644 --- a/test/napi/src/js/typedarrays.rs +++ b/test/napi/src/js/typedarrays.rs @@ -201,16 +201,14 @@ where F: FnOnce( &mut FunctionContext<'cx>, Handle<'cx, JsUint32Array>, - ) -> NeonResult>> + ) -> NeonResult>>, { let mut a = cx.argument::(0)?; let detach = cx.argument::(1)?; let before = typed_array_info(&mut cx, a)?; - detach.call_with(&cx) - .arg(a) - .exec(&mut cx)?; + detach.call_with(&cx).arg(a).exec(&mut cx)?; if let Some(new_array) = f(&mut cx, a)? { a = new_array; @@ -227,12 +225,12 @@ where } pub fn detach_same_handle(cx: FunctionContext) -> JsResult { - detach_and_then(cx, |_, _| { Ok(None) }) + detach_and_then(cx, |_, _| Ok(None)) } pub fn detach_and_escape(cx: FunctionContext) -> JsResult { detach_and_then(cx, |cx, a| { - let a = cx.compute_scoped(|_| { Ok(a) })?; + let a = cx.compute_scoped(|_| Ok(a))?; Ok(Some(a)) }) } From 28b61364f4e2d850a6d1cad4ba271fdf58bbfbfb Mon Sep 17 00:00:00 2001 From: David Herman Date: Sat, 23 Jul 2022 15:07:38 -0700 Subject: [PATCH 24/31] prettier --- test/napi/lib/typedarrays.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/test/napi/lib/typedarrays.js b/test/napi/lib/typedarrays.js index 4434a68bc..622707322 100644 --- a/test/napi/lib/typedarrays.js +++ b/test/napi/lib/typedarrays.js @@ -1,10 +1,10 @@ var addon = require(".."); var assert = require("chai").assert; -const { Worker, isMainThread, parentPort } = require('worker_threads'); +const { Worker, isMainThread, parentPort } = require("worker_threads"); if (!isMainThread) { - parentPort.on('message', (message) => { + parentPort.on("message", (message) => { // transfer it back parentPort.postMessage(message, [message]); }); @@ -34,12 +34,12 @@ function detach(buffer) { reject = rej; }); - DETACH_WORKER.once('message', (message) => { + DETACH_WORKER.once("message", (message) => { resolve(message); }); return promise; -}; +} describe("Typed arrays", function () { it("correctly reads a TypedArray using the borrow API", function () { @@ -421,7 +421,13 @@ describe("Typed arrays", function () { assert.strictEqual(addon.get_arraybuffer_byte_length(buf), 0); }); - function testDetach(arr, addonFn, byteLengthBefore, lengthBefore, byteOffsetBefore) { + function testDetach( + arr, + addonFn, + byteLengthBefore, + lengthBefore, + byteOffsetBefore + ) { let { before, after } = addonFn(arr, (arr) => detach(arr.buffer)); assert.strictEqual(before.byteLength, byteLengthBefore); From 912b178bb8a0e607d87212e596742db9b4a1f605 Mon Sep 17 00:00:00 2001 From: David Herman Date: Sat, 23 Jul 2022 15:10:55 -0700 Subject: [PATCH 25/31] fix doc test --- crates/neon/src/types_impl/buffer/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/neon/src/types_impl/buffer/mod.rs b/crates/neon/src/types_impl/buffer/mod.rs index e4121be25..892926ea2 100644 --- a/crates/neon/src/types_impl/buffer/mod.rs +++ b/crates/neon/src/types_impl/buffer/mod.rs @@ -199,7 +199,7 @@ impl ResultExt for Result { /// # Example /// /// ``` -/// # use crate::prelude::*; +/// # use neon::prelude::*; /// # fn f(mut cx: FunctionContext) -> JsResult { /// // Allocate a 16-byte ArrayBuffer and a uint32 array of length 2 (i.e., 8 bytes) /// // starting at byte offset 4 of the buffer: @@ -214,7 +214,7 @@ impl ResultExt for Result { /// // arr: | | | /// // +-------+-------+ /// // 0 1 2 -/// let buf = cx.array_buffer(16); +/// let buf = cx.array_buffer(16)?; /// let arr = JsUint32Array::from_region(&mut cx, buf.region(4, 2))?; /// # Ok(arr) /// # } From f24f1e70aeb2bdbb8cc42e9343efa85d62dd2cd4 Mon Sep 17 00:00:00 2001 From: David Herman Date: Mon, 1 Aug 2022 11:15:38 -0700 Subject: [PATCH 26/31] Update for latest RFC changes: - byte_offset -> offset - byte_length -> size - to_region -> region - to_typed_array and region take &self - JsArrayBuffer::region static method, for doc discoverability --- crates/neon/src/types_impl/buffer/mod.rs | 22 +++---- crates/neon/src/types_impl/buffer/types.rs | 76 +++++++++++++--------- test/napi/src/js/typedarrays.rs | 22 +++---- 3 files changed, 69 insertions(+), 51 deletions(-) diff --git a/crates/neon/src/types_impl/buffer/mod.rs b/crates/neon/src/types_impl/buffer/mod.rs index 892926ea2..d133dcea5 100644 --- a/crates/neon/src/types_impl/buffer/mod.rs +++ b/crates/neon/src/types_impl/buffer/mod.rs @@ -90,7 +90,7 @@ pub trait TypedArray: private::Sealed { C: Context<'cx>; /// Returns the size, in bytes, of the allocated binary data. - fn byte_length<'cx, C>(&self, cx: &mut C) -> usize + fn size<'cx, C>(&self, cx: &mut C) -> usize where C: Context<'cx>; } @@ -190,7 +190,7 @@ impl ResultExt for Result { /// /// A `Region` can be created via the /// [`Handle::region()`](crate::handle::Handle::region) or -/// [`JsTypedArray::to_region()`](crate::types::JsTypedArray::to_region) methods. +/// [`JsTypedArray::region()`](crate::types::JsTypedArray::region) methods. /// /// A region is **not** checked for validity until it is converted /// a typed array via [`to_typed_array()`](Region::to_typed_array) or @@ -222,7 +222,7 @@ impl ResultExt for Result { #[derive(Clone, Copy)] pub struct Region<'cx, T: Binary> { pub(super) buffer: Handle<'cx, JsArrayBuffer>, - pub(super) byte_offset: usize, + pub(super) offset: usize, pub(super) len: usize, pub(super) phantom: PhantomData, } @@ -234,8 +234,8 @@ impl<'cx, T: Binary> Region<'cx, T> { } /// Returns the starting byte offset of the region. - pub fn byte_offset(self) -> usize { - self.byte_offset + pub fn offset(self) -> usize { + self.offset } /// Returns the number of elements of type `T` in the region. @@ -243,25 +243,25 @@ impl<'cx, T: Binary> Region<'cx, T> { self.len } - /// Returns the byte length of the region, which is equal to + /// Returns the size of the region in bytes, which is equal to /// `(self.len() * size_of::())`. - pub fn byte_length(self) -> usize { + pub fn size(self) -> usize { self.len * std::mem::size_of::() } /// Constructs a typed array for this buffer region. /// - /// The resulting typed array has `self.len()` elements and byte length - /// `self.byte_length()`. + /// The resulting typed array has `self.len()` elements and a size of + /// `self.size()` bytes. /// /// Throws an exception if the region is invalid, for example if the starting /// offset is not properly aligned, or the length goes beyond the end of the /// buffer. - pub fn to_typed_array<'c, C>(self, cx: &mut C) -> JsResult<'c, JsTypedArray> + pub fn to_typed_array<'c, C>(&self, cx: &mut C) -> JsResult<'c, JsTypedArray> where C: Context<'c>, { - JsTypedArray::from_region(cx, self) + JsTypedArray::from_region(cx, *self) } } diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index b503bb480..6fc695a3a 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -157,7 +157,7 @@ impl TypedArray for JsBuffer { }) } - fn byte_length<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { + fn size<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { unsafe { sys::buffer::size(cx.env().to_raw(), self.to_raw()) } } } @@ -208,21 +208,45 @@ impl JsArrayBuffer { Handle::new_internal(Self(value)) } + + /// Returns a region of this buffer. + /// + /// See also: [`Handle::region()`](Handle::region) for a more + /// ergonomic form of this method. + pub fn region<'cx, T: Binary>( + buffer: &Handle<'cx, JsArrayBuffer>, + offset: usize, + len: usize, + ) -> Region<'cx, T> { + buffer.region(offset, len) + } } impl<'cx> Handle<'cx, JsArrayBuffer> { /// Returns a [`Region`](crate::types::buffer::Region) representing a typed - /// region of this buffer, starting at `byte_offset` and containing `len` - /// elements of type `T`. + /// region of this buffer, starting at `offset` and containing `len` elements + /// of type `T`. /// /// The region is **not** checked for validity by this method. Regions are only /// validated when they are converted to typed arrays. /// + /// # Example + /// + /// ``` + /// # use neon::prelude::*; + /// # fn f(mut cx: FunctionContext) -> JsResult { + /// let buf: Handle = cx.argument(0)?; + /// let region = buf.region::(64, 8); + /// println!("offset={}, len={}, size={}", region.offset(), region.len(), region.size()); + /// # Ok(cx.undefined()) + /// # } + /// ``` + /// /// See the [`Region`](Region) documentation for more information. - pub fn region(self, byte_offset: usize, len: usize) -> Region<'cx, T> { + pub fn region(&self, offset: usize, len: usize) -> Region<'cx, T> { Region { - buffer: self, - byte_offset, + buffer: *self, + offset, len, phantom: PhantomData, } @@ -303,7 +327,7 @@ impl TypedArray for JsArrayBuffer { }) } - fn byte_length<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { + fn size<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { unsafe { sys::arraybuffer::size(cx.env().to_raw(), self.to_raw()) } } } @@ -513,7 +537,7 @@ impl TypedArray for JsTypedArray { } } - fn byte_length<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { + fn size<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { self.len(cx) * std::mem::size_of::() } } @@ -521,7 +545,7 @@ impl TypedArray for JsTypedArray { impl JsTypedArray { /// Constructs a typed array that views `buffer`. /// - /// The resulting typed array has `(buffer.byte_length() / size_of::())` elements. + /// The resulting typed array has `(buffer.size() / size_of::())` elements. pub fn from_buffer<'cx, 'b: 'cx, C>( cx: &mut C, buffer: Handle<'b, JsArrayBuffer>, @@ -529,11 +553,11 @@ impl JsTypedArray { where C: Context<'cx>, { - let byte_length = buffer.byte_length(cx); + let size = buffer.size(cx); let elt_size = std::mem::size_of::(); - let len = byte_length / elt_size; + let len = size / elt_size; - if (len * elt_size) != byte_length { + if (len * elt_size) != size { panic!( "byte length of typed array should be a multiple of {}", elt_size @@ -545,8 +569,8 @@ impl JsTypedArray { /// Constructs a typed array for the specified buffer region. /// - /// The resulting typed array has `region.len()` elements and byte length - /// `region.byte_length()`. + /// The resulting typed array has `region.len()` elements and a size of + /// `region.size()` bytes. /// /// Throws an exception if the region is invalid, for example if the starting /// offset is not properly aligned, or the length goes beyond the end of the @@ -557,19 +581,13 @@ impl JsTypedArray { { let Region { buffer, - byte_offset, + offset, len, .. } = region; let result = unsafe { - sys::typedarray::new( - cx.env().to_raw(), - T::TYPE_TAG, - buffer.to_raw(), - byte_offset, - len, - ) + sys::typedarray::new(cx.env().to_raw(), T::TYPE_TAG, buffer.to_raw(), offset, len) }; if let Ok(arr) = result { @@ -584,7 +602,7 @@ impl JsTypedArray { } /// Returns information about the backing buffer region for this typed array. - pub fn to_region<'cx, C>(&self, cx: &mut C) -> Region<'cx, T> + pub fn region<'cx, C>(&self, cx: &mut C) -> Region<'cx, T> where C: Context<'cx>, { @@ -593,7 +611,7 @@ impl JsTypedArray { Region { buffer: Handle::new_internal(JsArrayBuffer::from_raw(cx.env(), info.buf)), - byte_offset: info.offset, + offset: info.offset, len: info.length, phantom: PhantomData, } @@ -615,8 +633,8 @@ impl JsTypedArray { /// for this typed array. /// /// Note that the typed array might only reference a region of the buffer; use the - /// [`byte_offset()`](JsTypedArray::byte_offset) and - /// [`byte_length()`](crate::types::buffer::TypedArray::byte_length) methods to + /// [`offset()`](JsTypedArray::offset) and + /// [`size()`](crate::types::buffer::TypedArray::size) methods to /// determine the region. pub fn buffer<'cx, C>(&self, cx: &mut C) -> Handle<'cx, JsArrayBuffer> where @@ -627,7 +645,7 @@ impl JsTypedArray { /// Returns the offset (in bytes) of the typed array from the start of its /// [`JsArrayBuffer`](JsArrayBuffer). - pub fn byte_offset<'cx, C>(&self, cx: &mut C) -> usize + pub fn offset<'cx, C>(&self, cx: &mut C) -> usize where C: Context<'cx>, { @@ -638,10 +656,10 @@ impl JsTypedArray { /// Returns the length of the typed array, i.e. the number of elements. /// /// Note that, depending on the element size, this is not necessarily the same as - /// [`byte_length()`](crate::types::buffer::TypedArray::byte_length). In particular: + /// [`size()`](crate::types::buffer::TypedArray::size). In particular: /// /// ```ignore - /// self.byte_length() == self.len() * size_of::() + /// self.size() == self.len() * size_of::() /// ``` #[allow(clippy::len_without_is_empty)] pub fn len<'cx, C>(&self, cx: &mut C) -> usize diff --git a/test/napi/src/js/typedarrays.rs b/test/napi/src/js/typedarrays.rs index 1573d8b05..d2036de3c 100644 --- a/test/napi/src/js/typedarrays.rs +++ b/test/napi/src/js/typedarrays.rs @@ -154,17 +154,17 @@ pub fn return_uint32array_from_arraybuffer_region( mut cx: FunctionContext, ) -> JsResult { let buf = cx.argument::(0)?; - let byte_offset = cx.argument::(1)?; - let byte_offset = byte_offset.value(&mut cx); + let offset = cx.argument::(1)?; + let offset = offset.value(&mut cx); let len = cx.argument::(2)?; let len = len.value(&mut cx); - JsUint32Array::from_region(&mut cx, buf.region(byte_offset as usize, len as usize)) + JsUint32Array::from_region(&mut cx, buf.region(offset as usize, len as usize)) } pub fn get_arraybuffer_byte_length(mut cx: FunctionContext) -> JsResult { let buf = cx.argument::(0)?; - let byte_length = buf.byte_length(&mut cx); - let n = cx.number(byte_length as u32); + let size = buf.size(&mut cx); + let n = cx.number(size as u32); Ok(n) } @@ -175,22 +175,22 @@ fn typed_array_info<'cx, C, T: Binary>( where C: Context<'cx>, { - let byte_offset = a.byte_offset(cx); - let byte_offset = cx.number(byte_offset as u32); + let offset = a.offset(cx); + let offset = cx.number(offset as u32); let len = a.len(cx); let len = cx.number(len as u32); - let byte_length = a.byte_length(cx); - let byte_length = cx.number(byte_length as u32); + let size = a.size(cx); + let size = cx.number(size as u32); let buffer = a.buffer(cx); let obj = cx.empty_object(); - obj.set(cx, "byteOffset", byte_offset)?; + obj.set(cx, "byteOffset", offset)?; obj.set(cx, "length", len)?; - obj.set(cx, "byteLength", byte_length)?; + obj.set(cx, "byteLength", size)?; obj.set(cx, "buffer", buffer)?; Ok(obj) From 74251cb82365423c83df348da0776b8572c416ba Mon Sep 17 00:00:00 2001 From: David Herman Date: Mon, 1 Aug 2022 13:55:24 -0700 Subject: [PATCH 27/31] - API tweak: TypedArray::Item must implement Binary - add tests about region validation --- crates/neon/src/types_impl/buffer/mod.rs | 2 +- test/napi/lib/typedarrays.js | 52 ++++++++++++++++++++++++ test/napi/src/js/typedarrays.rs | 38 +++++++++++++++++ test/napi/src/lib.rs | 2 + 4 files changed, 93 insertions(+), 1 deletion(-) diff --git a/crates/neon/src/types_impl/buffer/mod.rs b/crates/neon/src/types_impl/buffer/mod.rs index d133dcea5..1c9a9da68 100644 --- a/crates/neon/src/types_impl/buffer/mod.rs +++ b/crates/neon/src/types_impl/buffer/mod.rs @@ -48,7 +48,7 @@ pub use types::Binary; /// /// [typed-arrays]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays pub trait TypedArray: private::Sealed { - type Item; + type Item: Binary; /// Statically checked immutable borrow of binary data. /// diff --git a/test/napi/lib/typedarrays.js b/test/napi/lib/typedarrays.js index 622707322..469413756 100644 --- a/test/napi/lib/typedarrays.js +++ b/test/napi/lib/typedarrays.js @@ -487,4 +487,56 @@ describe("Typed arrays", function () { var buf = new ArrayBuffer(16); testDetach(new Uint32Array(buf, 4, 2), addon.detach_and_unroot, 8, 2, 4); }); + + it("doesn't validate regions without instantiating", function () { + var buf = new ArrayBuffer(64); + + try { + addon.build_f32_region(buf, 1, 4, false); + } catch (e) { + assert.fail("misaligned region shouldn't be validated without instantiating"); + } + + try { + addon.build_f32_region(buf, 0, 20, false); + } catch (e) { + assert.fail("region overrun shouldn't be validated without instantiating"); + } + + try { + addon.build_f64_region(buf, 1, 4, false); + } catch (e) { + assert.fail("misaligned region shouldn't be validated without instantiating"); + } + + try { + addon.build_f64_region(buf, 0, 10, false); + } catch (e) { + assert.fail("region overrun shouldn't be validated without instantiating"); + } + }); + + it("validates regions when instantiating", function () { + var buf = new ArrayBuffer(64); + + try { + addon.build_f32_region(buf, 1, 4, false); + assert.fail("misaligned region should be validated when instantiating"); + } catch (expected) { } + + try { + addon.build_f32_region(buf, 0, 20, false); + assert.fail("region overrun should be validated when instantiating"); + } catch (expected) { } + + try { + addon.build_f64_region(buf, 1, 4, true); + assert.fail("misaligned region should be validated when instantiating"); + } catch (expected) { } + + try { + addon.build_f64_region(buf, 0, 10, true); + assert.fail("region overrun should be validated when instantiating"); + } catch (expected) { } + }) }); diff --git a/test/napi/src/js/typedarrays.rs b/test/napi/src/js/typedarrays.rs index d2036de3c..3597cc1c2 100644 --- a/test/napi/src/js/typedarrays.rs +++ b/test/napi/src/js/typedarrays.rs @@ -279,6 +279,44 @@ pub fn get_typed_array_info(mut cx: FunctionContext) -> JsResult { } } +pub fn build_f32_region(mut cx: FunctionContext) -> JsResult { + let buf: Handle = cx.argument(0)?; + let offset: Handle = cx.argument(1)?; + let offset: usize = offset.value(&mut cx) as u32 as usize; + let len: Handle = cx.argument(2)?; + let len: usize = len.value(&mut cx) as u32 as usize; + let convert: Handle = cx.argument(3)?; + let convert: bool = convert.value(&mut cx); + + let region = buf.region::(offset, len); + + if convert { + let arr = region.to_typed_array(&mut cx)?; + Ok(arr.upcast()) + } else { + Ok(cx.undefined().upcast()) + } +} + +pub fn build_f64_region(mut cx: FunctionContext) -> JsResult { + let buf: Handle = cx.argument(0)?; + let offset: Handle = cx.argument(1)?; + let offset: usize = offset.value(&mut cx) as u32 as usize; + let len: Handle = cx.argument(2)?; + let len: usize = len.value(&mut cx) as u32 as usize; + let convert: Handle = cx.argument(3)?; + let convert: bool = convert.value(&mut cx); + + let region = buf.region::(offset, len); + + if convert { + let arr = region.to_typed_array(&mut cx)?; + Ok(arr.upcast()) + } else { + Ok(cx.undefined().upcast()) + } +} + pub fn read_buffer_with_lock(mut cx: FunctionContext) -> JsResult { let b: Handle = cx.argument(0)?; let i = cx.argument::(1)?.value(&mut cx) as usize; diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index 8b7e2b409..607baa48a 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -264,6 +264,8 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { cx.export_function("detach_and_cast", detach_and_cast)?; cx.export_function("detach_and_unroot", detach_and_unroot)?; cx.export_function("get_typed_array_info", get_typed_array_info)?; + cx.export_function("build_f32_region", build_f32_region)?; + cx.export_function("build_f64_region", build_f64_region)?; cx.export_function("read_buffer_with_lock", read_buffer_with_lock)?; cx.export_function("read_buffer_with_borrow", read_buffer_with_borrow)?; cx.export_function("write_buffer_with_lock", write_buffer_with_lock)?; From 326a9d02e0affaf9e25db6d259a4059a965248d5 Mon Sep 17 00:00:00 2001 From: David Herman Date: Mon, 1 Aug 2022 13:59:43 -0700 Subject: [PATCH 28/31] prettier --- test/napi/lib/typedarrays.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/test/napi/lib/typedarrays.js b/test/napi/lib/typedarrays.js index 469413756..e1ecad187 100644 --- a/test/napi/lib/typedarrays.js +++ b/test/napi/lib/typedarrays.js @@ -494,25 +494,33 @@ describe("Typed arrays", function () { try { addon.build_f32_region(buf, 1, 4, false); } catch (e) { - assert.fail("misaligned region shouldn't be validated without instantiating"); + assert.fail( + "misaligned region shouldn't be validated without instantiating" + ); } try { addon.build_f32_region(buf, 0, 20, false); } catch (e) { - assert.fail("region overrun shouldn't be validated without instantiating"); + assert.fail( + "region overrun shouldn't be validated without instantiating" + ); } try { addon.build_f64_region(buf, 1, 4, false); } catch (e) { - assert.fail("misaligned region shouldn't be validated without instantiating"); + assert.fail( + "misaligned region shouldn't be validated without instantiating" + ); } try { addon.build_f64_region(buf, 0, 10, false); } catch (e) { - assert.fail("region overrun shouldn't be validated without instantiating"); + assert.fail( + "region overrun shouldn't be validated without instantiating" + ); } }); @@ -522,21 +530,21 @@ describe("Typed arrays", function () { try { addon.build_f32_region(buf, 1, 4, false); assert.fail("misaligned region should be validated when instantiating"); - } catch (expected) { } + } catch (expected) {} try { addon.build_f32_region(buf, 0, 20, false); assert.fail("region overrun should be validated when instantiating"); - } catch (expected) { } + } catch (expected) {} try { addon.build_f64_region(buf, 1, 4, true); assert.fail("misaligned region should be validated when instantiating"); - } catch (expected) { } + } catch (expected) {} try { addon.build_f64_region(buf, 0, 10, true); assert.fail("region overrun should be validated when instantiating"); - } catch (expected) { } - }) + } catch (expected) {} + }); }); From aa6c65506aa4c841a6cce80af40f801de69b26ee Mon Sep 17 00:00:00 2001 From: David Herman Date: Sun, 28 Aug 2022 15:00:23 -0700 Subject: [PATCH 29/31] Address review suggestions: - make doc-related crates (doc-comment, aquamarine) optional - `Binary` only requires `Clone`, not `Copy` or `Debug` - `Region` API changes * still impls Copy but all its methods take a reference * range checks throw `RangeError` instead of panicking - minor code improvements * copy-editing nit in doc comment * `buffer/types.rs` imports from `types_impl` not `types` so it's clear why it has private access * use `map_err` to clean up some code * simplify `detach` test helper function --- .cargo/config.toml | 4 +- crates/neon/Cargo.toml | 8 +++- crates/neon/src/types_docs.rs | 35 +++++++------- crates/neon/src/types_impl/buffer/mod.rs | 40 +++++++++++----- crates/neon/src/types_impl/buffer/types.rs | 55 ++++++++++++---------- test/napi/lib/typedarrays.js | 13 +---- test/napi/src/js/typedarrays.rs | 2 +- 7 files changed, 86 insertions(+), 71 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 4f56ff8cf..0ed60eaa5 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,5 +3,5 @@ # The following aliases simplify linting the entire workspace neon-check = " check --all --all-targets --features napi-experimental,futures" neon-clippy = "clippy --all --all-targets --features napi-experimental,futures -- -A clippy::missing_safety_doc" -neon-test = " test --all --features=napi-experimental,futures" -neon-doc = " rustdoc -p neon --features=napi-experimental,futures -- --cfg docsrs" +neon-test = " test --all --features=doc-comment,napi-experimental,futures" +neon-doc = " rustdoc -p neon --features=doc-dependencies,napi-experimental,futures -- --cfg docsrs" diff --git a/crates/neon/Cargo.toml b/crates/neon/Cargo.toml index 78b6240f5..0b673d609 100644 --- a/crates/neon/Cargo.toml +++ b/crates/neon/Cargo.toml @@ -25,8 +25,8 @@ semver = "1" smallvec = "1.4.2" once_cell = "1.10.0" neon-macros = { version = "=1.0.0-alpha.1", path = "../neon-macros" } -aquamarine = "0.1.11" -doc-comment = "0.3.3" +aquamarine = { version = "0.1.11", optional = true } +doc-comment = { version = "0.3.3", optional = true } [dependencies.tokio] version = "1.18.2" @@ -72,9 +72,13 @@ task-api = [] # DEPRECATED: This is always enabled and should be removed. proc-macros = [] +# Enables the optional dependencies that are only used for generating the API docs. +doc-dependencies = ["doc-comment", "aquamarine"] + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] features = [ "futures", "napi-experimental", + "doc-dependencies", ] diff --git a/crates/neon/src/types_docs.rs b/crates/neon/src/types_docs.rs index 4a0d207f3..f32582e58 100644 --- a/crates/neon/src/types_docs.rs +++ b/crates/neon/src/types_docs.rs @@ -1,10 +1,10 @@ -#[cfg_attr(doc, aquamarine::aquamarine)] +#[cfg_attr(aquamarine, aquamarine::aquamarine)] /// Representations of JavaScript's core builtin types. /// /// ## Modeling JavaScript Types /// -/// All JavaScript values in Neon implement the abstract [`Value`](Value) trait, which -/// is the most generic way to work with JavaScript values. Neon provides a +/// All JavaScript values in Neon implement the abstract [`Value`](crate::types::Value) +/// trait, which is the most generic way to work with JavaScript values. Neon provides a /// number of types that implement this trait, each representing a particular /// type of JavaScript value. /// @@ -52,9 +52,9 @@ /// ### The JavaScript Type Hierarchy /// /// The top of the JavaScript type hierarchy is modeled with the Neon type -/// [`JsValue`](JsValue). A [handle](crate::handle) to a `JsValue` can refer -/// to any JavaScript value. (For TypeScript programmers, this can be thought -/// of as similar to TypeScript's [`unknown`][unknown] type.) +/// [`JsValue`](crate::types::JsValue). A [handle](crate::handle) to a `JsValue` +/// can refer to any JavaScript value. (For TypeScript programmers, this can be +/// thought of as similar to TypeScript's [`unknown`][unknown] type.) /// /// From there, the type hierarchy divides into _object types_ and _primitive /// types_: @@ -80,12 +80,13 @@ /// JsValue-->primitives /// ``` /// -/// The top of the object type hierarchy is [`JsObject`](JsObject). A handle to a -/// `JsObject` can refer to any JavaScript object. +/// The top of the object type hierarchy is [`JsObject`](crate::types::JsObject). A +/// handle to a `JsObject` can refer to any JavaScript object. /// /// The primitive types are the built-in JavaScript datatypes that are not object -/// types: [`JsBoolean`](JsBoolean), [`JsNumber`](JsNumber), [`JsString`](JsString), -/// [`JsNull`](JsNull), and [`JsUndefined`](JsUndefined). +/// types: [`JsBoolean`](crate::types::JsBoolean), [`JsNumber`](crate::types::JsNumber), +/// [`JsString`](crate::types::JsString), [`JsNull`](crate::types::JsNull), and +/// [`JsUndefined`](crate::types::JsUndefined). /// /// #### Object Types /// @@ -123,12 +124,14 @@ /// ``` /// /// These include several categories of object types: -/// - **Standard object types:** [`JsFunction`](JsFunction), [`JsArray`](JsArray), -/// [`JsDate`](JsDate), and [`JsError`](JsError). -/// - **Typed arrays:** [`JsBuffer`](JsBuffer), [`JsArrayBuffer`](JsArrayBuffer), -/// and [`JsTypedArray`](JsTypedArray). -/// - **Custom types:** [`JsBox`](JsBox), a special Neon type that allows the creation -/// of custom objects that own Rust data structures. +/// - **Standard object types:** [`JsFunction`](crate::types::JsFunction), +/// [`JsArray`](crate::types::JsArray), [`JsDate`](crate::types::JsDate), and +/// [`JsError`](crate::types::JsError). +/// - **Typed arrays:** [`JsBuffer`](crate::types::JsBuffer), +/// [`JsArrayBuffer`](crate::types::JsArrayBuffer), and +/// [`JsTypedArray`](crate::types::JsTypedArray). +/// - **Custom types:** [`JsBox`](crate::types::JsBox), a special Neon type that allows +/// the creation of custom objects that own Rust data structures. /// /// All object types implement the [`Object`](crate::object::Object) trait, which /// allows getting and setting properties of an object. diff --git a/crates/neon/src/types_impl/buffer/mod.rs b/crates/neon/src/types_impl/buffer/mod.rs index 1c9a9da68..20b3a1cd8 100644 --- a/crates/neon/src/types_impl/buffer/mod.rs +++ b/crates/neon/src/types_impl/buffer/mod.rs @@ -192,7 +192,7 @@ impl ResultExt for Result { /// [`Handle::region()`](crate::handle::Handle::region) or /// [`JsTypedArray::region()`](crate::types::JsTypedArray::region) methods. /// -/// A region is **not** checked for validity until it is converted +/// A region is **not** checked for validity until it is converted to /// a typed array via [`to_typed_array()`](Region::to_typed_array) or /// [`JsTypedArray::from_region()`](crate::types::JsTypedArray::from_region). /// @@ -215,37 +215,37 @@ impl ResultExt for Result { /// // +-------+-------+ /// // 0 1 2 /// let buf = cx.array_buffer(16)?; -/// let arr = JsUint32Array::from_region(&mut cx, buf.region(4, 2))?; +/// let arr = JsUint32Array::from_region(&mut cx, &buf.region(4, 2))?; /// # Ok(arr) /// # } /// ``` #[derive(Clone, Copy)] pub struct Region<'cx, T: Binary> { - pub(super) buffer: Handle<'cx, JsArrayBuffer>, - pub(super) offset: usize, - pub(super) len: usize, - pub(super) phantom: PhantomData, + buffer: Handle<'cx, JsArrayBuffer>, + offset: usize, + len: usize, + phantom: PhantomData, } impl<'cx, T: Binary> Region<'cx, T> { /// Returns the handle to the region's buffer. - pub fn buffer(self) -> Handle<'cx, JsArrayBuffer> { + pub fn buffer(&self) -> Handle<'cx, JsArrayBuffer> { self.buffer } /// Returns the starting byte offset of the region. - pub fn offset(self) -> usize { + pub fn offset(&self) -> usize { self.offset } /// Returns the number of elements of type `T` in the region. - pub fn len(self) -> usize { + pub fn len(&self) -> usize { self.len } /// Returns the size of the region in bytes, which is equal to /// `(self.len() * size_of::())`. - pub fn size(self) -> usize { + pub fn size(&self) -> usize { self.len * std::mem::size_of::() } @@ -261,21 +261,37 @@ impl<'cx, T: Binary> Region<'cx, T> { where C: Context<'c>, { - JsTypedArray::from_region(cx, *self) + JsTypedArray::from_region(cx, self) } } mod private { use super::Binary; use crate::sys::raw; + use std::fmt::{Debug, Formatter}; use std::marker::PhantomData; pub trait Sealed {} - #[derive(Debug, Clone)] + #[derive(Clone)] pub struct JsTypedArrayInner { pub(super) local: raw::Local, pub(super) buffer: raw::Local, pub(super) _type: PhantomData, } + + impl Debug for JsTypedArrayInner { + fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { + f.write_str("JsTypedArrayInner { ")?; + f.write_str("local: ")?; + self.local.fmt(f)?; + f.write_str(", buffer: ")?; + self.buffer.fmt(f)?; + f.write_str(", _type: PhantomData")?; + f.write_str(" }")?; + Ok(()) + } + } + + impl Copy for JsTypedArrayInner {} } diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index 6fc695a3a..1bbc944a8 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -6,16 +6,25 @@ use crate::{ object::Object, result::{JsResult, Throw}, sys::{self, raw, TypedArrayType}, - types::buffer::{ - lock::{Ledger, Lock}, - private::{self, JsTypedArrayInner}, - BorrowError, Ref, RefMut, Region, TypedArray, + types_impl::{ + buffer::{ + lock::{Ledger, Lock}, + private::{self, JsTypedArrayInner}, + BorrowError, Ref, RefMut, Region, TypedArray, + }, + private::ValueInternal, + Value, }, - types::{private::ValueInternal, Value}, }; +#[cfg(feature = "doc-comment")] use doc_comment::doc_comment; +#[cfg(not(feature = "doc-comment"))] +macro_rules! doc_comment { + {$comment:expr, $decl:item} => { $decl }; +} + /// The Node [`Buffer`](https://nodejs.org/api/buffer.html) type. /// /// # Example @@ -335,13 +344,11 @@ impl TypedArray for JsArrayBuffer { /// A marker trait for all possible element types of binary buffers. /// /// This trait can only be implemented within the Neon library. -pub trait Binary: private::Sealed + Copy + std::fmt::Debug { +pub trait Binary: private::Sealed + Clone { /// The internal Node-API enum value for this binary type. const TYPE_TAG: TypedArrayType; } -impl Copy for JsTypedArrayInner {} - /// The family of JS [typed array][typed-arrays] types. /// /// ## Typed Arrays @@ -558,13 +565,13 @@ impl JsTypedArray { let len = size / elt_size; if (len * elt_size) != size { - panic!( + return cx.throw_range_error(format!( "byte length of typed array should be a multiple of {}", elt_size - ); + )); } - Self::from_region(cx, buffer.region(0, len)) + Self::from_region(cx, &buffer.region(0, len)) } /// Constructs a typed array for the specified buffer region. @@ -575,30 +582,26 @@ impl JsTypedArray { /// Throws an exception if the region is invalid, for example if the starting /// offset is not properly aligned, or the length goes beyond the end of the /// buffer. - pub fn from_region<'c, 'r, C>(cx: &mut C, region: Region<'r, T>) -> JsResult<'c, Self> + pub fn from_region<'c, 'r, C>(cx: &mut C, region: &Region<'r, T>) -> JsResult<'c, Self> where C: Context<'c>, { - let Region { + let &Region { buffer, offset, len, .. } = region; - let result = unsafe { + let arr = (unsafe { sys::typedarray::new(cx.env().to_raw(), T::TYPE_TAG, buffer.to_raw(), offset, len) - }; - - if let Ok(arr) = result { - Ok(Handle::new_internal(Self(JsTypedArrayInner { - local: arr, - buffer: buffer.to_raw(), - _type: PhantomData, - }))) - } else { - Err(Throw::new()) - } + }).map_err(|_| Throw::new())?; + + Ok(Handle::new_internal(Self(JsTypedArrayInner { + local: arr, + buffer: buffer.to_raw(), + _type: PhantomData, + }))) } /// Returns information about the backing buffer region for this typed array. @@ -626,7 +629,7 @@ impl JsTypedArray { C: Context<'cx>, { let buffer = cx.array_buffer(len * std::mem::size_of::())?; - Self::from_region(cx, buffer.region(0, len)) + Self::from_region(cx, &buffer.region(0, len)) } /// Returns the [`JsArrayBuffer`](JsArrayBuffer) that owns the underlying storage buffer diff --git a/test/napi/lib/typedarrays.js b/test/napi/lib/typedarrays.js index e1ecad187..ac5fd8475 100644 --- a/test/napi/lib/typedarrays.js +++ b/test/napi/lib/typedarrays.js @@ -27,18 +27,7 @@ function detach(buffer) { DETACH_WORKER.postMessage(buffer, [buffer]); - let resolve, reject; - - let promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - - DETACH_WORKER.once("message", (message) => { - resolve(message); - }); - - return promise; + return new Promise(resolve => DETACH_WORKER.once("message", resolve)); } describe("Typed arrays", function () { diff --git a/test/napi/src/js/typedarrays.rs b/test/napi/src/js/typedarrays.rs index 3597cc1c2..572a4e7f2 100644 --- a/test/napi/src/js/typedarrays.rs +++ b/test/napi/src/js/typedarrays.rs @@ -158,7 +158,7 @@ pub fn return_uint32array_from_arraybuffer_region( let offset = offset.value(&mut cx); let len = cx.argument::(2)?; let len = len.value(&mut cx); - JsUint32Array::from_region(&mut cx, buf.region(offset as usize, len as usize)) + JsUint32Array::from_region(&mut cx, &buf.region(offset as usize, len as usize)) } pub fn get_arraybuffer_byte_length(mut cx: FunctionContext) -> JsResult { From f6c21437abfc50aefcd7ee36174f3b20b1ca552b Mon Sep 17 00:00:00 2001 From: David Herman Date: Sun, 28 Aug 2022 15:20:19 -0700 Subject: [PATCH 30/31] Satisfying the linters with ritualistic sacrifices --- crates/neon/src/types_impl/buffer/mod.rs | 1 + crates/neon/src/types_impl/buffer/types.rs | 3 ++- test/napi/lib/typedarrays.js | 2 +- test/napi/src/js/functions.rs | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/neon/src/types_impl/buffer/mod.rs b/crates/neon/src/types_impl/buffer/mod.rs index 20b3a1cd8..6942e33b8 100644 --- a/crates/neon/src/types_impl/buffer/mod.rs +++ b/crates/neon/src/types_impl/buffer/mod.rs @@ -239,6 +239,7 @@ impl<'cx, T: Binary> Region<'cx, T> { } /// Returns the number of elements of type `T` in the region. + #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { self.len } diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs index 1bbc944a8..a84c964c3 100644 --- a/crates/neon/src/types_impl/buffer/types.rs +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -595,7 +595,8 @@ impl JsTypedArray { let arr = (unsafe { sys::typedarray::new(cx.env().to_raw(), T::TYPE_TAG, buffer.to_raw(), offset, len) - }).map_err(|_| Throw::new())?; + }) + .map_err(|_| Throw::new())?; Ok(Handle::new_internal(Self(JsTypedArrayInner { local: arr, diff --git a/test/napi/lib/typedarrays.js b/test/napi/lib/typedarrays.js index ac5fd8475..e53fc0ed4 100644 --- a/test/napi/lib/typedarrays.js +++ b/test/napi/lib/typedarrays.js @@ -27,7 +27,7 @@ function detach(buffer) { DETACH_WORKER.postMessage(buffer, [buffer]); - return new Promise(resolve => DETACH_WORKER.once("message", resolve)); + return new Promise((resolve) => DETACH_WORKER.once("message", resolve)); } describe("Typed arrays", function () { diff --git a/test/napi/src/js/functions.rs b/test/napi/src/js/functions.rs index f3422cfb7..0a099774a 100644 --- a/test/napi/src/js/functions.rs +++ b/test/napi/src/js/functions.rs @@ -160,7 +160,7 @@ pub fn num_arguments(mut cx: FunctionContext) -> JsResult { } pub fn return_this(mut cx: FunctionContext) -> JsResult { - Ok(cx.this()?) + cx.this() } pub fn require_object_this(mut cx: FunctionContext) -> JsResult { From 7fb1779033ee0b7ce91c8364e0e543917a7e8464 Mon Sep 17 00:00:00 2001 From: David Herman Date: Wed, 14 Sep 2022 10:26:05 -0700 Subject: [PATCH 31/31] Doc build fix: cfg_attr should do a feature test --- crates/neon/src/types_docs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/neon/src/types_docs.rs b/crates/neon/src/types_docs.rs index f32582e58..6e8f4f28f 100644 --- a/crates/neon/src/types_docs.rs +++ b/crates/neon/src/types_docs.rs @@ -1,4 +1,4 @@ -#[cfg_attr(aquamarine, aquamarine::aquamarine)] +#[cfg_attr(feature = "aquamarine", aquamarine::aquamarine)] /// Representations of JavaScript's core builtin types. /// /// ## Modeling JavaScript Types