From 14dd8b83e61d0dee93a70a4293e39c8a6ee06e34 Mon Sep 17 00:00:00 2001 From: Ouyang Yadong Date: Fri, 10 Jul 2020 19:14:32 +0800 Subject: [PATCH] feat(napi): add BigInt related apis --- napi/src/env.rs | 39 +++++++++++ napi/src/js_values/bigint.rs | 85 +++++++++++++++++++++++ napi/src/js_values/mod.rs | 8 +-- test_module/__test__/napi6/bigint.spec.js | 30 ++++++++ test_module/src/lib.rs | 23 ++++++ test_module/src/napi6/bigint.rs | 47 +++++++++++++ test_module/src/napi6/mod.rs | 1 + 7 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 napi/src/js_values/bigint.rs create mode 100644 test_module/__test__/napi6/bigint.spec.js create mode 100644 test_module/src/napi6/bigint.rs create mode 100644 test_module/src/napi6/mod.rs diff --git a/napi/src/env.rs b/napi/src/env.rs index 9c37a071..441f00db 100644 --- a/napi/src/env.rs +++ b/napi/src/env.rs @@ -92,6 +92,45 @@ impl Env { Ok(JsNumber::from_raw_unchecked(self.0, raw_value)) } + #[cfg(napi6)] + #[inline] + /// https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_int64 + pub fn create_bigint_from_i64(&self, value: i64) -> Result { + let mut raw_value = ptr::null_mut(); + check_status(unsafe { sys::napi_create_bigint_int64(self.0, value, &mut raw_value) })?; + Ok(JsBigint::from_raw_unchecked(self.0, raw_value)) + } + + #[cfg(napi6)] + #[inline] + /// https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_words + pub fn create_bigint_from_u64(&self, value: u64) -> Result { + let mut raw_value = ptr::null_mut(); + check_status(unsafe { sys::napi_create_bigint_uint64(self.0, value, &mut raw_value) })?; + Ok(JsBigint::from_raw_unchecked(self.0, raw_value)) + } + + #[cfg(napi6)] + #[inline] + /// https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_words + /// The resulting BigInt will be negative when sign_bit is true. + pub fn create_bigint_from_words(&self, sign_bit: bool, words: Vec) -> Result { + let mut raw_value = ptr::null_mut(); + check_status(unsafe { + sys::napi_create_bigint_words( + self.0, + match sign_bit { + true => 1, + false => 0, + }, + words.len() as u64, + words.as_ptr(), + &mut raw_value, + ) + })?; + Ok(JsBigint::from_raw_unchecked(self.0, raw_value)) + } + #[inline] pub fn create_string(&self, s: &str) -> Result { self.create_string_from_chars(s.as_ptr() as *const _, s.len() as u64) diff --git a/napi/src/js_values/bigint.rs b/napi/src/js_values/bigint.rs new file mode 100644 index 00000000..1001e709 --- /dev/null +++ b/napi/src/js_values/bigint.rs @@ -0,0 +1,85 @@ +use std::convert::TryFrom; +use std::ptr; + +use super::{Error, Value}; +use crate::error::check_status; +use crate::{sys, Result}; + +#[derive(Debug)] +pub struct JsBigint(pub(crate) Value); + +/// The BigInt will be converted losslessly when the value is over what an int64 could hold. +impl TryFrom for i64 { + type Error = Error; + + fn try_from(value: JsBigint) -> Result { + value.get_i64().map(|(v, _)| v) + } +} + +/// The BigInt will be converted losslessly when the value is over what an uint64 could hold. +impl TryFrom for u64 { + type Error = Error; + + fn try_from(value: JsBigint) -> Result { + value.get_u64().map(|(v, _)| v) + } +} + +impl JsBigint { + /// https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_words + pub fn get_words(&self, sign_bit: bool) -> Result> { + let mut word_count: u64 = 0; + check_status(unsafe { + sys::napi_get_value_bigint_words( + self.0.env, + self.0.value, + ptr::null_mut(), + &mut word_count, + ptr::null_mut(), + ) + })?; + + let mut words: Vec = Vec::with_capacity(word_count as usize); + let mut sign_bit = match sign_bit { + true => 1, + false => 0, + }; + check_status(unsafe { + sys::napi_get_value_bigint_words( + self.0.env, + self.0.value, + &mut sign_bit, + &mut word_count, + words.as_mut_ptr(), + ) + })?; + + unsafe { + words.set_len(word_count as usize); + }; + + Ok(words) + } + + #[inline] + pub fn get_u64(&self) -> Result<(u64, bool)> { + let mut val: u64 = 0; + let mut loss = false; + check_status(unsafe { + sys::napi_get_value_bigint_uint64(self.0.env, self.0.value, &mut val, &mut loss) + })?; + + Ok((val, loss)) + } + + #[inline] + pub fn get_i64(&self) -> Result<(i64, bool)> { + let mut val: i64 = 0; + let mut loss: bool = false; + check_status(unsafe { + sys::napi_get_value_bigint_int64(self.0.env, self.0.value, &mut val, &mut loss) + })?; + Ok((val, loss)) + } +} diff --git a/napi/src/js_values/mod.rs b/napi/src/js_values/mod.rs index f0963d25..c0946557 100644 --- a/napi/src/js_values/mod.rs +++ b/napi/src/js_values/mod.rs @@ -5,6 +5,8 @@ use crate::error::check_status; use crate::{sys, Error, Result, Status}; mod arraybuffer; +#[cfg(napi6)] +mod bigint; mod boolean; mod buffer; mod class_property; @@ -20,6 +22,8 @@ mod value_ref; mod value_type; pub use arraybuffer::JsArrayBuffer; +#[cfg(napi6)] +pub use bigint::JsBigint; pub use boolean::JsBoolean; pub use buffer::JsBuffer; pub use class_property::Property; @@ -41,10 +45,6 @@ pub struct JsUnknown(pub(crate) Value); #[derive(Debug)] pub struct JsNull(pub(crate) Value); -#[cfg(napi6)] -#[derive(Debug)] -pub struct JsBigint(pub(crate) Value); - #[derive(Debug)] pub struct JsSymbol(pub(crate) Value); diff --git a/test_module/__test__/napi6/bigint.spec.js b/test_module/__test__/napi6/bigint.spec.js new file mode 100644 index 00000000..a462f7f1 --- /dev/null +++ b/test_module/__test__/napi6/bigint.spec.js @@ -0,0 +1,30 @@ +const test = require('ava') + +const napiVersion = require('../napi-version') +const bindings = require('../../index.node') + +test('should create bigints', (t) => { + if (napiVersion >= 6) { + t.is(bindings.testCreateBigintFromI64(), BigInt('9223372036854775807')) + t.is(bindings.testCreateBigintFromU64(), BigInt('18446744073709551615')) + t.is( + bindings.testCreateBigintFromWords(), + BigInt('-340282366920938463463374607431768211455'), + ) + } else { + t.is(bindings.testCreateBigintFromI64, undefined) + } +}) + +test('should get integers from bigints', (t) => { + if (napiVersion >= 6) { + t.is(bindings.testGetBigintI64(BigInt('-123')), -123) + t.is(bindings.testGetBigintU64(BigInt(123)), 123) + t.deepEqual(bindings.testGetBigintWords(), [ + BigInt('9223372036854775807'), + BigInt('9223372036854775807'), + ]) + } else { + t.is(bindings.testGetBigintI64, undefined) + } +}) diff --git a/test_module/src/lib.rs b/test_module/src/lib.rs index 9123aa6f..6a0225e0 100644 --- a/test_module/src/lib.rs +++ b/test_module/src/lib.rs @@ -13,6 +13,8 @@ mod napi4; mod napi5; #[cfg(napi4)] mod tokio_rt; +#[cfg(napi6)] +mod napi6; mod buffer; mod class; @@ -39,6 +41,15 @@ use symbol::{create_named_symbol, create_symbol_from_js_string, create_unnamed_s use task::test_spawn_thread; #[cfg(napi4)] use tokio_rt::{error_from_tokio_future, test_execute_tokio_readfile}; +#[cfg(napi6)] +use napi6::bigint::{ + test_create_bigint_from_i64, + test_create_bigint_from_u64, + test_create_bigint_from_words, + test_get_bigint_i64, + test_get_bigint_u64, + test_get_bigint_words, +}; register_module!(test_module, init); @@ -74,6 +85,18 @@ fn init(module: &mut Module) -> Result<()> { module.create_named_method("uvReadFile", uv_read_file)?; #[cfg(napi5)] module.create_named_method("testObjectIsDate", test_object_is_date)?; + #[cfg(napi6)] + module.create_named_method("testCreateBigintFromI64", test_create_bigint_from_i64)?; + #[cfg(napi6)] + module.create_named_method("testCreateBigintFromU64", test_create_bigint_from_u64)?; + #[cfg(napi6)] + module.create_named_method("testCreateBigintFromWords", test_create_bigint_from_words)?; + #[cfg(napi6)] + module.create_named_method("testGetBigintI64", test_get_bigint_i64)?; + #[cfg(napi6)] + module.create_named_method("testGetBigintU64", test_get_bigint_u64)?; + #[cfg(napi6)] + module.create_named_method("testGetBigintWords", test_get_bigint_words)?; Ok(()) } diff --git a/test_module/src/napi6/bigint.rs b/test_module/src/napi6/bigint.rs new file mode 100644 index 00000000..1d8781bd --- /dev/null +++ b/test_module/src/napi6/bigint.rs @@ -0,0 +1,47 @@ +use std::convert::TryFrom; +use napi::{CallContext, JsBigint, Result, JsNumber, JsObject}; + +#[js_function(0)] +pub fn test_create_bigint_from_i64(ctx: CallContext) -> Result { + ctx.env.create_bigint_from_i64(i64::max_value()) +} + +#[js_function(0)] +pub fn test_create_bigint_from_u64(ctx: CallContext) -> Result { + ctx.env.create_bigint_from_u64(u64::max_value()) +} + +#[js_function(0)] +pub fn test_create_bigint_from_words(ctx: CallContext) -> Result { + ctx.env.create_bigint_from_words(true, vec![u64::max_value(), u64::max_value()]) +} + +#[js_function(1)] +pub fn test_get_bigint_i64(ctx: CallContext) -> Result { + let js_bigint = ctx.get::(0)?; + let val = i64::try_from(js_bigint)?; + ctx.env.create_int32(val as i32) +} + +#[js_function(1)] +pub fn test_get_bigint_u64(ctx: CallContext) -> Result { + let js_bigint = ctx.get::(0)?; + let val = u64::try_from(js_bigint)?; + ctx.env.create_int32(val as i32) +} + +#[js_function(0)] +pub fn test_get_bigint_words(ctx: CallContext) -> Result { + let js_bigint = ctx.env.create_bigint_from_words(true, vec![i64::max_value() as u64, i64::max_value() as u64])?; + let mut js_arr = ctx.env.create_array_with_length(2)?; + let words = js_bigint.get_words(true)?; + js_arr.set_number_indexed_property( + ctx.env.create_int64(0)?, + ctx.env.create_bigint_from_u64(words[0])? + )?; + js_arr.set_number_indexed_property( + ctx.env.create_int64(1)?, + ctx.env.create_bigint_from_u64(words[1])? + )?; + Ok(js_arr) +} diff --git a/test_module/src/napi6/mod.rs b/test_module/src/napi6/mod.rs new file mode 100644 index 00000000..8f8d1485 --- /dev/null +++ b/test_module/src/napi6/mod.rs @@ -0,0 +1 @@ +pub mod bigint;