Merge pull request #846 from napi-rs/bigint

feat(napi): BigInt codegen support
This commit is contained in:
LongYinan 2021-11-11 16:48:08 +08:00 committed by GitHub
commit 9a8484144c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 313 additions and 53 deletions

View file

@ -241,9 +241,9 @@ yarn test
| Result<()> | Error | 1 | v8.0.0 |
| T: Fn(...) -> Result<T> | Function | 1 | v8.0.0 |
| Async/Future | Promise<T> | 4 | v10.6.0 | async |
| Task | Promise<T> | 1 | v8.5.0 |
| AsyncTask | Promise<T> | 1 | v8.5.0 |
| (NOT YET) | global | 1 | v8.0.0 |
| (NOT YET) | Symbol | 1 | v8.0.0 |
| JsSymbol | Symbol | 1 | v8.0.0 |
| (NOT YET) | ArrayBuffer/TypedArray | 1 | v8.0.0 |
| (NOT YET) | threadsafe function | 4 | v10.6.0 | napi4 |
| (NOT YET) | BigInt | 6 | v10.7.0 | napi6 |
| BigInt | BigInt | 6 | v10.7.0 | napi6 |

View file

@ -40,10 +40,12 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
("u16", "number"),
("u32", "number"),
("u64", "BigInt"),
("i64n", "BigInt"),
("u128", "BigInt"),
("i128", "BigInt"),
("usize", "BigInt"),
("isize", "BigInt"),
("JsBigInt", "BigInt"),
("BigInt", "BigInt"),
("bool", "boolean"),
("String", "string"),

View file

@ -1,7 +1,10 @@
use crate::{check_status, sys, Error, JsUnknown, NapiRaw, NapiValue, Result, Status, ValueType};
use std::ptr;
use crate::{check_status, sys, Error, JsUnknown, NapiRaw, NapiValue, Result, Status, ValueType};
mod array;
#[cfg(feature = "napi6")]
mod bigint;
mod boolean;
mod buffer;
mod either;
@ -15,6 +18,8 @@ mod string;
mod task;
pub use array::*;
#[cfg(feature = "napi6")]
pub use bigint::*;
pub use buffer::*;
pub use either::*;
pub use nil::*;

View file

@ -0,0 +1,211 @@
/// We don't implement `FromNapiValue` for `i64` `u64` `i128` `u128` `isize` `usize` here
/// Because converting directly from `JsBigInt` to these values may result in a loss of precision and thus unintended behavior
/// ```rust
/// use napi::{bindgen_prelude::*, JsBigint};
///
/// #[napi]
/// fn bigint_add(mut a: Bigint, mut b: Bigint) -> u128 {
/// a.get_u128().1 + b.get_u128().1 // We have opportunity to check if the `u128` has lost precision
/// }
/// ```
use std::ptr;
use crate::{check_status, sys};
use super::{FromNapiValue, ToNapiValue, TypeName};
/// i64 is converted to `Number`
#[repr(transparent)]
#[allow(non_camel_case_types)]
pub struct i64n(pub i64);
/// https://nodejs.org/api/n-api.html#napi_create_bigint_words
/// The resulting BigInt is calculated as: (1)^sign_bit (words[0] × (2^64)^0 + words[1] × (2^64)^1 + …)
pub struct BigInt {
/// true for negative numbers
pub sign_bit: bool,
pub words: Vec<u64>,
}
impl TypeName for BigInt {
fn type_name() -> &'static str {
"BigInt"
}
fn value_type() -> crate::ValueType {
crate::ValueType::BigInt
}
}
impl FromNapiValue for BigInt {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result<Self> {
let mut word_count = 0usize;
check_status!(sys::napi_get_value_bigint_words(
env,
napi_val,
ptr::null_mut(),
&mut word_count,
ptr::null_mut(),
))?;
let mut words: Vec<u64> = Vec::with_capacity(word_count as usize);
let mut sign_bit = 0;
check_status!(sys::napi_get_value_bigint_words(
env,
napi_val,
&mut sign_bit,
&mut word_count,
words.as_mut_ptr(),
))?;
words.set_len(word_count as usize);
Ok(BigInt {
sign_bit: sign_bit == 1,
words,
})
}
}
impl BigInt {
/// (signed, value, lossless)
/// get the first word of the BigInt as `u64`
/// return true in the last element of tuple if the value is lossless
/// or the value is truncated
pub fn get_u64(&self) -> (bool, u64, bool) {
(
self.sign_bit,
self.words[0],
self.sign_bit && self.words.len() == 1,
)
}
/// (value, lossless)
/// get the first word of the BigInt as `i64`
/// return true if the value is lossless
/// or the value is truncated
pub fn get_i64(&self) -> (i64, bool) {
let val = self.words[0] as i64;
(val, val as u64 == self.words[0] && self.words.len() == 1)
}
/// (value, lossless)
/// get the first two words of the BigInt as `i128`
/// return true if the value is lossless
/// or the value is truncated
pub fn get_i128(&self) -> (i128, bool) {
let len = self.words.len();
if len == 1 {
(self.words[0] as i128, false)
} else {
let i128_words: [i64; 2] = [self.words[0] as _, self.words[1] as _];
let mut val = unsafe { ptr::read(i128_words.as_ptr() as *const i128) };
if self.sign_bit {
val = -val;
}
(val, len > 2)
}
}
/// (signed, value, lossless)
/// get the first two words of the BigInt as `u128`
/// return true if the value is lossless
/// or the value is truncated
pub fn get_u128(&self) -> (bool, u128, bool) {
let len = self.words.len();
if len == 1 {
(self.sign_bit, self.words[0] as u128, false)
} else {
let u128_words: [u64; 2] = [self.words[0], self.words[1]];
let val = unsafe { ptr::read(u128_words.as_ptr() as *const u128) };
(self.sign_bit, val, len > 2)
}
}
}
impl ToNapiValue for BigInt {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> crate::Result<sys::napi_value> {
let mut raw_value = ptr::null_mut();
let len = val.words.len();
check_status!(sys::napi_create_bigint_words(
env,
match val.sign_bit {
true => 1,
false => 0,
},
len,
val.words.as_ptr(),
&mut raw_value,
))?;
Ok(raw_value)
}
}
impl ToNapiValue for i128 {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> crate::Result<sys::napi_value> {
let mut raw_value = ptr::null_mut();
let sign_bit = if val > 0 { 0 } else { 1 };
let words = &val as *const i128 as *const u64;
check_status!(sys::napi_create_bigint_words(
env,
sign_bit,
2,
words,
&mut raw_value
))?;
Ok(raw_value)
}
}
impl ToNapiValue for u128 {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> crate::Result<sys::napi_value> {
let mut raw_value = ptr::null_mut();
let words = &val as *const u128 as *const u64;
check_status!(sys::napi_create_bigint_words(
env,
0,
2,
words,
&mut raw_value
))?;
Ok(raw_value)
}
}
impl ToNapiValue for i64n {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> crate::Result<sys::napi_value> {
let mut raw_value = ptr::null_mut();
check_status!(sys::napi_create_bigint_int64(env, val.0, &mut raw_value))?;
Ok(raw_value)
}
}
impl ToNapiValue for u64 {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> crate::Result<sys::napi_value> {
let mut raw_value = ptr::null_mut();
check_status!(sys::napi_create_bigint_uint64(env, val, &mut raw_value))?;
Ok(raw_value)
}
}
impl ToNapiValue for usize {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> crate::Result<sys::napi_value> {
let mut raw_value = ptr::null_mut();
check_status!(sys::napi_create_bigint_uint64(
env,
val as u64,
&mut raw_value
))?;
Ok(raw_value)
}
}
impl ToNapiValue for isize {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> crate::Result<sys::napi_value> {
let mut raw_value = ptr::null_mut();
check_status!(sys::napi_create_bigint_int64(
env,
val as i64,
&mut raw_value
))?;
Ok(raw_value)
}
}

View file

@ -56,7 +56,7 @@ impl FromNapiValue for Value {
}
}
#[cfg(feature = "napi6")]
ValueType::Bigint => todo!(),
ValueType::BigInt => todo!(),
_ => Value::Null,
};

View file

@ -100,43 +100,43 @@ impl Env {
/// [n_api_napi_create_bigint_int64](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_int64)
#[cfg(feature = "napi6")]
pub fn create_bigint_from_i64(&self, value: i64) -> Result<JsBigint> {
pub fn create_bigint_from_i64(&self, value: i64) -> Result<JsBigInt> {
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, 1))
Ok(JsBigInt::from_raw_unchecked(self.0, raw_value, 1))
}
#[cfg(feature = "napi6")]
pub fn create_bigint_from_u64(&self, value: u64) -> Result<JsBigint> {
pub fn create_bigint_from_u64(&self, value: u64) -> Result<JsBigInt> {
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, 1))
Ok(JsBigInt::from_raw_unchecked(self.0, raw_value, 1))
}
#[cfg(feature = "napi6")]
pub fn create_bigint_from_i128(&self, value: i128) -> Result<JsBigint> {
pub fn create_bigint_from_i128(&self, value: i128) -> Result<JsBigInt> {
let mut raw_value = ptr::null_mut();
let sign_bit = if value > 0 { 0 } else { 1 };
let words = &value as *const i128 as *const u64;
check_status!(unsafe {
sys::napi_create_bigint_words(self.0, sign_bit, 2, words, &mut raw_value)
})?;
Ok(JsBigint::from_raw_unchecked(self.0, raw_value, 1))
Ok(JsBigInt::from_raw_unchecked(self.0, raw_value, 1))
}
#[cfg(feature = "napi6")]
pub fn create_bigint_from_u128(&self, value: u128) -> Result<JsBigint> {
pub fn create_bigint_from_u128(&self, value: u128) -> Result<JsBigInt> {
let mut raw_value = ptr::null_mut();
let words = &value as *const u128 as *const u64;
check_status!(unsafe { sys::napi_create_bigint_words(self.0, 0, 2, words, &mut raw_value) })?;
Ok(JsBigint::from_raw_unchecked(self.0, raw_value, 1))
Ok(JsBigInt::from_raw_unchecked(self.0, raw_value, 1))
}
/// [n_api_napi_create_bigint_words](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_words)
///
/// The resulting BigInt will be negative when sign_bit is true.
#[cfg(feature = "napi6")]
pub fn create_bigint_from_words(&self, sign_bit: bool, words: Vec<u64>) -> Result<JsBigint> {
pub fn create_bigint_from_words(&self, sign_bit: bool, words: Vec<u64>) -> Result<JsBigInt> {
let mut raw_value = ptr::null_mut();
let len = words.len();
check_status!(unsafe {
@ -151,7 +151,7 @@ impl Env {
&mut raw_value,
)
})?;
Ok(JsBigint::from_raw_unchecked(self.0, raw_value, len))
Ok(JsBigInt::from_raw_unchecked(self.0, raw_value, len))
}
pub fn create_string(&self, s: &str) -> Result<JsString> {

View file

@ -5,22 +5,22 @@ use super::*;
use crate::{bindgen_runtime::TypeName, check_status, sys, Result};
#[derive(Clone, Copy)]
pub struct JsBigint {
pub struct JsBigInt {
pub(crate) raw: Value,
pub word_count: usize,
}
impl TypeName for JsBigint {
impl TypeName for JsBigInt {
fn type_name() -> &'static str {
"BigInt"
}
fn value_type() -> ValueType {
ValueType::Bigint
ValueType::BigInt
}
}
impl JsBigint {
impl JsBigInt {
pub(crate) fn from_raw_unchecked(
env: sys::napi_env,
value: sys::napi_value,
@ -76,7 +76,6 @@ impl JsBigint {
}))
}
#[cfg(feature = "napi5")]
pub fn is_date(&self) -> Result<bool> {
let mut is_date = true;
check_status!(unsafe { sys::napi_is_date(self.raw.env, self.raw.value, &mut is_date) })?;
@ -122,19 +121,19 @@ impl JsBigint {
}
}
impl NapiRaw for JsBigint {
impl NapiRaw for JsBigInt {
unsafe fn raw(&self) -> sys::napi_value {
self.raw.value
}
}
impl<'env> NapiRaw for &'env JsBigint {
impl<'env> NapiRaw for &'env JsBigInt {
unsafe fn raw(&self) -> sys::napi_value {
self.raw.value
}
}
impl NapiValue for JsBigint {
impl NapiValue for JsBigInt {
unsafe fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self> {
let mut word_count = 0usize;
check_status!(sys::napi_get_value_bigint_words(
@ -144,11 +143,11 @@ impl NapiValue for JsBigint {
&mut word_count,
ptr::null_mut(),
))?;
Ok(JsBigint {
Ok(JsBigInt {
raw: Value {
env,
value,
value_type: ValueType::Bigint,
value_type: ValueType::BigInt,
},
word_count,
})
@ -167,11 +166,11 @@ impl NapiValue for JsBigint {
Status::from(status) == Status::Ok,
"napi_get_value_bigint_words failed"
);
JsBigint {
JsBigInt {
raw: Value {
env,
value,
value_type: ValueType::Bigint,
value_type: ValueType::BigInt,
},
word_count,
}
@ -179,24 +178,24 @@ impl NapiValue for JsBigint {
}
/// The BigInt will be converted losslessly when the value is over what an int64 could hold.
impl TryFrom<JsBigint> for i64 {
impl TryFrom<JsBigInt> for i64 {
type Error = Error;
fn try_from(value: JsBigint) -> Result<i64> {
fn try_from(value: JsBigInt) -> Result<i64> {
value.get_i64().map(|(v, _)| v)
}
}
/// The BigInt will be converted losslessly when the value is over what an uint64 could hold.
impl TryFrom<JsBigint> for u64 {
impl TryFrom<JsBigInt> for u64 {
type Error = Error;
fn try_from(value: JsBigint) -> Result<u64> {
fn try_from(value: JsBigInt) -> Result<u64> {
value.get_u64().map(|(v, _)| v)
}
}
impl JsBigint {
impl JsBigInt {
/// https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_words
pub fn get_words(&mut self) -> Result<(bool, Vec<u64>)> {
let mut words: Vec<u64> = Vec::with_capacity(self.word_count as usize);
@ -221,21 +220,21 @@ impl JsBigint {
pub fn get_u64(&self) -> Result<(u64, bool)> {
let mut val: u64 = 0;
let mut loss = false;
let mut lossless = false;
check_status!(unsafe {
sys::napi_get_value_bigint_uint64(self.raw.env, self.raw.value, &mut val, &mut loss)
sys::napi_get_value_bigint_uint64(self.raw.env, self.raw.value, &mut val, &mut lossless)
})?;
Ok((val, loss))
Ok((val, lossless))
}
pub fn get_i64(&self) -> Result<(i64, bool)> {
let mut val: i64 = 0;
let mut loss: bool = false;
let mut lossless: bool = false;
check_status!(unsafe {
sys::napi_get_value_bigint_int64(self.raw.env, self.raw.value, &mut val, &mut loss)
sys::napi_get_value_bigint_int64(self.raw.env, self.raw.value, &mut val, &mut lossless)
})?;
Ok((val, loss))
Ok((val, lossless))
}
pub fn get_i128(&mut self) -> Result<(i128, bool)> {

View file

@ -4,7 +4,7 @@ use serde::de::Visitor;
use serde::de::{DeserializeSeed, EnumAccess, MapAccess, SeqAccess, Unexpected, VariantAccess};
#[cfg(feature = "napi6")]
use crate::JsBigint;
use crate::JsBigInt;
use crate::{type_of, NapiValue, Value, ValueType};
use crate::{
Error, JsBoolean, JsBufferValue, JsNumber, JsObject, JsString, JsUnknown, Result, Status,
@ -54,8 +54,8 @@ impl<'x, 'de, 'env> serde::de::Deserializer<'x> for &'de mut De<'env> {
}
}
#[cfg(feature = "napi6")]
ValueType::Bigint => {
let mut js_bigint = unsafe { JsBigint::from_raw(self.0.env, self.0.value)? };
ValueType::BigInt => {
let mut js_bigint = unsafe { JsBigInt::from_raw(self.0.env, self.0.value)? };
let (signed, v, _loss) = js_bigint.get_u128()?;
if signed {
visitor.visit_i128(-(v as i128))

View file

@ -33,7 +33,7 @@ mod value_ref;
pub use arraybuffer::*;
#[cfg(feature = "napi6")]
pub use bigint::JsBigint;
pub use bigint::JsBigInt;
pub use boolean::JsBoolean;
pub use buffer::*;
#[cfg(feature = "napi5")]

View file

@ -15,7 +15,7 @@ pub enum ValueType {
Function = 7,
External = 8,
#[cfg(feature = "napi6")]
Bigint = 9,
BigInt = 9,
Unknown = 1024,
}
@ -30,7 +30,7 @@ impl From<i32> for ValueType {
fn from(value: i32) -> ValueType {
match value {
#[cfg(feature = "napi6")]
sys::ValueType::napi_bigint => ValueType::Bigint,
sys::ValueType::napi_bigint => ValueType::BigInt,
sys::ValueType::napi_boolean => ValueType::Boolean,
sys::ValueType::napi_external => ValueType::External,
sys::ValueType::napi_function => ValueType::Function,

View file

@ -1,28 +1,28 @@
use napi::{CallContext, JsBigint, JsNumber, JsObject, Result};
use napi::{CallContext, JsBigInt, JsNumber, JsObject, Result};
use std::convert::TryFrom;
#[js_function]
pub fn test_create_bigint_from_i64(ctx: CallContext) -> Result<JsBigint> {
pub fn test_create_bigint_from_i64(ctx: CallContext) -> Result<JsBigInt> {
ctx.env.create_bigint_from_i64(i64::max_value())
}
#[js_function]
pub fn test_create_bigint_from_u64(ctx: CallContext) -> Result<JsBigint> {
pub fn test_create_bigint_from_u64(ctx: CallContext) -> Result<JsBigInt> {
ctx.env.create_bigint_from_u64(u64::max_value())
}
#[js_function]
pub fn test_create_bigint_from_i128(ctx: CallContext) -> Result<JsBigint> {
pub fn test_create_bigint_from_i128(ctx: CallContext) -> Result<JsBigInt> {
ctx.env.create_bigint_from_i128(i128::max_value())
}
#[js_function]
pub fn test_create_bigint_from_u128(ctx: CallContext) -> Result<JsBigint> {
pub fn test_create_bigint_from_u128(ctx: CallContext) -> Result<JsBigInt> {
ctx.env.create_bigint_from_u128(u128::max_value())
}
#[js_function]
pub fn test_create_bigint_from_words(ctx: CallContext) -> Result<JsBigint> {
pub fn test_create_bigint_from_words(ctx: CallContext) -> Result<JsBigInt> {
ctx
.env
.create_bigint_from_words(true, vec![u64::max_value(), u64::max_value()])
@ -30,14 +30,14 @@ pub fn test_create_bigint_from_words(ctx: CallContext) -> Result<JsBigint> {
#[js_function(1)]
pub fn test_get_bigint_i64(ctx: CallContext) -> Result<JsNumber> {
let js_bigint = ctx.get::<JsBigint>(0)?;
let js_bigint = ctx.get::<JsBigInt>(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<JsNumber> {
let js_bigint = ctx.get::<JsBigint>(0)?;
let js_bigint = ctx.get::<JsBigInt>(0)?;
let val = u64::try_from(js_bigint)?;
ctx.env.create_int32(val as i32)
}

View file

@ -13,6 +13,9 @@ Generated by [AVA](https://avajs.dev).
export function sumNums(nums: Array<number>): number␊
export function readFileAsync(path: string): Promise<Buffer>
export function asyncMultiTwo(arg: number): Promise<number>
export function bigintAdd(a: BigInt, b: BigInt): BigInt␊
export function createBigInt(): BigInt␊
export function createBigIntI64(): BigInt␊
export function getCwd(callback: (arg0: string) => void): void␊
export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void␊
export function eitherStringOrNumber(input: string | number): number␊

View file

@ -35,6 +35,9 @@ import {
withoutAbortController,
withAbortController,
asyncMultiTwo,
bigintAdd,
createBigInt,
createBigIntI64,
} from '../'
test('number', (t) => {
@ -217,3 +220,17 @@ MaybeTest('abort resolved task', async (t) => {
await withAbortController(1, 2, ctrl.signal).then(() => ctrl.abort())
t.pass('should not throw')
})
const BigIntTest = typeof BigInt !== 'undefined' ? test : test.skip
BigIntTest('BigInt add', (t) => {
t.is(bigintAdd(BigInt(1), BigInt(2)), BigInt(3))
})
BigIntTest('create BigInt', (t) => {
t.is(createBigInt(), BigInt('-3689348814741910323300'))
})
BigIntTest('create BigInt i64', (t) => {
t.is(createBigIntI64(), BigInt(100))
})

View file

@ -3,6 +3,9 @@ export function getNums(): Array<number>
export function sumNums(nums: Array<number>): number
export function readFileAsync(path: string): Promise<Buffer>
export function asyncMultiTwo(arg: number): Promise<number>
export function bigintAdd(a: BigInt, b: BigInt): BigInt
export function createBigInt(): BigInt
export function createBigIntI64(): BigInt
export function getCwd(callback: (arg0: string) => void): void
export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void
export function eitherStringOrNumber(input: string | number): number

View file

@ -0,0 +1,19 @@
use napi::bindgen_prelude::*;
#[napi]
fn bigint_add(a: BigInt, b: BigInt) -> u128 {
a.get_u128().1 + b.get_u128().1
}
#[napi]
fn create_big_int() -> BigInt {
BigInt {
words: vec![100u64, 200u64],
sign_bit: true,
}
}
#[napi]
fn create_big_int_i64() -> i64n {
i64n(100)
}

View file

@ -5,6 +5,7 @@ extern crate serde_derive;
mod array;
mod r#async;
mod bigint;
mod callback;
mod class;
mod class_factory;

View file

@ -4,7 +4,7 @@
"compilerOptions": {
"outDir": "./dist",
"rootDir": "__test__",
"target": "ES2015"
"target": "ES2018"
},
"exclude": ["dist", "index.d.ts"]
}