Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TryFrom<JsValue> for [iu](64|128) #3058

Merged
merged 3 commits into from Sep 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions crates/cli-support/src/intrinsic.rs
Expand Up @@ -80,6 +80,10 @@ fn opt_f64() -> Descriptor {
Descriptor::Option(Box::new(Descriptor::F64))
}

fn opt_i64() -> Descriptor {
Descriptor::Option(Box::new(Descriptor::I64))
}

intrinsics! {
pub enum Intrinsic {
#[symbol = "__wbindgen_jsval_eq"]
Expand Down Expand Up @@ -208,6 +212,9 @@ intrinsics! {
#[symbol = "__wbindgen_bigint_from_u128"]
#[signature = fn(U64, U64) -> Externref]
BigIntFromU128,
#[symbol = "__wbindgen_bigint_get_as_i64"]
#[signature = fn(ref_externref()) -> opt_i64()]
BigIntGetAsI64,
#[symbol = "__wbindgen_string_new"]
#[signature = fn(ref_string()) -> Externref]
StringNew,
Expand Down
6 changes: 6 additions & 0 deletions crates/cli-support/src/js/mod.rs
Expand Up @@ -3384,6 +3384,12 @@ impl<'a> Context<'a> {
format!("typeof(v) === 'boolean' ? (v ? 1 : 0) : 2")
}

Intrinsic::BigIntGetAsI64 => {
assert_eq!(args.len(), 1);
prelude.push_str(&format!("const v = {};\n", args[0]));
format!("typeof(v) === 'bigint' ? v : undefined")
}

Intrinsic::Throw => {
assert_eq!(args.len(), 1);
format!("throw new Error({})", args[0])
Expand Down
13 changes: 11 additions & 2 deletions crates/js-sys/src/lib.rs
Expand Up @@ -20,7 +20,7 @@

use core::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub};
use std::cmp::Ordering;
use std::convert::{self, Infallible};
use std::convert::{self, Infallible, TryFrom};
use std::f64;
use std::fmt;
use std::iter::{self, Product, Sum};
Expand Down Expand Up @@ -1046,7 +1046,7 @@ macro_rules! bigint_from {
impl PartialEq<$x> for BigInt {
#[inline]
fn eq(&self, other: &$x) -> bool {
JsValue::from(self) == BigInt::from(*other).unchecked_into::<JsValue>()
JsValue::from(self) == JsValue::from(BigInt::from(*other))
}
}
)*)
Expand All @@ -1068,6 +1068,15 @@ macro_rules! bigint_from_big {
self == &BigInt::from(*other)
}
}

impl TryFrom<BigInt> for $x {
type Error = BigInt;

#[inline]
fn try_from(x: BigInt) -> Result<Self, BigInt> {
Self::try_from(JsValue::from(x)).map_err(JsCast::unchecked_into)
}
}
)*)
}
bigint_from_big!(i64 u64 i128 u128);
Expand Down
57 changes: 57 additions & 0 deletions src/lib.rs
Expand Up @@ -886,6 +886,62 @@ macro_rules! big_numbers {
)*)
}

fn bigint_get_as_i64(v: &JsValue) -> Option<i64> {
unsafe { Option::from_abi(__wbindgen_bigint_get_as_i64(v.idx)) }
}

macro_rules! try_from_for_num64 {
($ty:ty) => {
impl TryFrom<JsValue> for $ty {
type Error = JsValue;

#[inline]
fn try_from(v: JsValue) -> Result<Self, JsValue> {
bigint_get_as_i64(&v)
// Reinterpret bits; ABI-wise this is safe to do and allows us to avoid
// having separate intrinsics per signed/unsigned types.
.map(|as_i64| as_i64 as Self)
// Double-check that we didn't truncate the bigint to 64 bits.
.filter(|as_self| v == *as_self)
// Not a bigint or not in range.
.ok_or(v)
}
}
};
}

try_from_for_num64!(i64);
try_from_for_num64!(u64);

macro_rules! try_from_for_num128 {
($ty:ty, $hi_ty:ty) => {
impl TryFrom<JsValue> for $ty {
type Error = JsValue;

#[inline]
fn try_from(v: JsValue) -> Result<Self, JsValue> {
// Truncate the bigint to 64 bits, this will give us the lower part.
let lo = match bigint_get_as_i64(&v) {
// The lower part must be interpreted as unsigned in both i128 and u128.
Some(lo) => lo as u64,
// Not a bigint.
None => return Err(v),
};
// Now we know it's a bigint, so we can safely use `>> 64n` without
// worrying about a JS exception on type mismatch.
let hi = v >> JsValue::from(64_u64);
// The high part is the one we want checked against a 64-bit range.
// If it fits, then our original number is in the 128-bit range.
let hi = <$hi_ty>::try_from(hi)?;
Ok(Self::from(hi) << 64 | Self::from(lo))
}
}
};
}

try_from_for_num128!(i128, i64);
try_from_for_num128!(u128, u64);

big_numbers! {
|n|,
i64 = __wbindgen_bigint_from_i64(n),
Expand Down Expand Up @@ -980,6 +1036,7 @@ externs! {
fn __wbindgen_number_get(idx: u32) -> WasmOption<f64>;
fn __wbindgen_boolean_get(idx: u32) -> u32;
fn __wbindgen_string_get(idx: u32) -> WasmSlice;
fn __wbindgen_bigint_get_as_i64(idx: u32) -> WasmOption<i64>;

fn __wbindgen_debug_string(ret: *mut [usize; 2], idx: u32) -> ();

Expand Down
29 changes: 29 additions & 0 deletions tests/wasm/bigint.rs
Expand Up @@ -87,3 +87,32 @@ pub fn u64_slice(a: &[u64]) -> Vec<u64> {
fn works() {
js_works();
}

mod try_from_works {
use super::*;
use crate::JsValue;
use core::convert::TryFrom;

macro_rules! test_type_boundaries {
($($ty:ident)*) => {
$(
#[wasm_bindgen_test]
fn $ty() {
// Not a bigint.
assert!($ty::try_from(JsValue::NULL).is_err());
assert!($ty::try_from(JsValue::from_f64(0.0)).is_err());
// Within range.
assert_eq!($ty::try_from(JsValue::from($ty::MIN)), Ok($ty::MIN));
// Too small.
assert!($ty::try_from(JsValue::from($ty::MIN) - JsValue::from(1_i64)).is_err());
// Within range.
assert_eq!($ty::try_from(JsValue::from($ty::MAX)), Ok($ty::MAX));
// Too large.
assert!($ty::try_from(JsValue::from($ty::MAX) + JsValue::from(1_i64)).is_err());
}
)*
};
}

test_type_boundaries!(i64 u64 i128 u128);
}