Skip to content

Commit

Permalink
Implement lossless TryFromJs for integers from f64 (#3907)
Browse files Browse the repository at this point in the history
* Implement lossless TryFromJs for integers from f64

* Use `num_traits::AsPrimitive`
  • Loading branch information
HalidOdat authored Jul 10, 2024
1 parent ab0854f commit 5b1621a
Showing 1 changed file with 97 additions and 0 deletions.
97 changes: 97 additions & 0 deletions core/engine/src/value/conversions/try_from_js.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! This module contains the [`TryFromJs`] trait, and conversions to basic Rust types.

use num_bigint::BigInt;
use num_traits::AsPrimitive;

use crate::{js_string, Context, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsValue};

Expand Down Expand Up @@ -156,6 +157,17 @@ impl TryFromJs for f64 {
}
}

fn from_f64<T>(v: f64) -> Option<T>
where
T: AsPrimitive<f64>,
f64: AsPrimitive<T>,
{
if <f64 as AsPrimitive<T>>::as_(v).as_().to_bits() == v.to_bits() {
return Some(v.as_());
}
None
}

impl TryFromJs for i8 {
fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
match value {
Expand All @@ -164,6 +176,11 @@ impl TryFromJs for i8 {
.with_message(format!("cannot convert value to a i8: {e}"))
.into()
}),
JsValue::Rational(f) => from_f64(*f).ok_or_else(|| {
JsNativeError::typ()
.with_message("cannot convert value to a i8")
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a i8")
.into()),
Expand All @@ -179,6 +196,11 @@ impl TryFromJs for u8 {
.with_message(format!("cannot convert value to a u8: {e}"))
.into()
}),
JsValue::Rational(f) => from_f64(*f).ok_or_else(|| {
JsNativeError::typ()
.with_message("cannot convert value to a u8")
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a u8")
.into()),
Expand All @@ -194,6 +216,11 @@ impl TryFromJs for i16 {
.with_message(format!("cannot convert value to a i16: {e}"))
.into()
}),
JsValue::Rational(f) => from_f64(*f).ok_or_else(|| {
JsNativeError::typ()
.with_message("cannot convert value to a i16")
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a i16")
.into()),
Expand All @@ -209,6 +236,11 @@ impl TryFromJs for u16 {
.with_message(format!("cannot convert value to a iu16: {e}"))
.into()
}),
JsValue::Rational(f) => from_f64(*f).ok_or_else(|| {
JsNativeError::typ()
.with_message("cannot convert value to a u16")
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a u16")
.into()),
Expand All @@ -220,6 +252,11 @@ impl TryFromJs for i32 {
fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
match value {
JsValue::Integer(i) => Ok(*i),
JsValue::Rational(f) => from_f64(*f).ok_or_else(|| {
JsNativeError::typ()
.with_message("cannot convert value to a i32")
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a i32")
.into()),
Expand All @@ -235,6 +272,11 @@ impl TryFromJs for u32 {
.with_message(format!("cannot convert value to a u32: {e}"))
.into()
}),
JsValue::Rational(f) => from_f64(*f).ok_or_else(|| {
JsNativeError::typ()
.with_message("cannot convert value to a u32")
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a u32")
.into()),
Expand All @@ -246,6 +288,11 @@ impl TryFromJs for i64 {
fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
match value {
JsValue::Integer(i) => Ok((*i).into()),
JsValue::Rational(f) => from_f64(*f).ok_or_else(|| {
JsNativeError::typ()
.with_message("cannot convert value to a i64")
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a i64")
.into()),
Expand All @@ -261,6 +308,11 @@ impl TryFromJs for u64 {
.with_message(format!("cannot convert value to a u64: {e}"))
.into()
}),
JsValue::Rational(f) => from_f64(*f).ok_or_else(|| {
JsNativeError::typ()
.with_message("cannot convert value to a u64")
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a u64")
.into()),
Expand All @@ -276,6 +328,11 @@ impl TryFromJs for usize {
.with_message(format!("cannot convert value to a usize: {e}"))
.into()
}),
JsValue::Rational(f) => from_f64(*f).ok_or_else(|| {
JsNativeError::typ()
.with_message("cannot convert value to a usize")
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a usize")
.into()),
Expand All @@ -287,6 +344,11 @@ impl TryFromJs for i128 {
fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
match value {
JsValue::Integer(i) => Ok((*i).into()),
JsValue::Rational(f) => from_f64(*f).ok_or_else(|| {
JsNativeError::typ()
.with_message("cannot convert value to a i128")
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a i128")
.into()),
Expand All @@ -302,13 +364,48 @@ impl TryFromJs for u128 {
.with_message(format!("cannot convert value to a u128: {e}"))
.into()
}),
JsValue::Rational(f) => from_f64(*f).ok_or_else(|| {
JsNativeError::typ()
.with_message("cannot convert value to a u128")
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a u128")
.into()),
}
}
}

#[test]
fn integer_floating_js_value_to_integer() {
let context = &mut Context::default();

assert_eq!(i8::try_from_js(&JsValue::from(4.0), context), Ok(4));
assert_eq!(u8::try_from_js(&JsValue::from(4.0), context), Ok(4));
assert_eq!(i16::try_from_js(&JsValue::from(4.0), context), Ok(4));
assert_eq!(u16::try_from_js(&JsValue::from(4.0), context), Ok(4));
assert_eq!(i32::try_from_js(&JsValue::from(4.0), context), Ok(4));
assert_eq!(u32::try_from_js(&JsValue::from(4.0), context), Ok(4));
assert_eq!(i64::try_from_js(&JsValue::from(4.0), context), Ok(4));
assert_eq!(u64::try_from_js(&JsValue::from(4.0), context), Ok(4));

// Floating with fractional part
let result = i32::try_from_js(&JsValue::from(4.000_000_000_000_001), context);
assert!(result.is_err());

// NaN
let result = i32::try_from_js(&JsValue::nan(), context);
assert!(result.is_err());

// +Infinity
let result = i32::try_from_js(&JsValue::positive_infinity(), context);
assert!(result.is_err());

// -Infinity
let result = i32::try_from_js(&JsValue::negative_infinity(), context);
assert!(result.is_err());
}

#[test]
fn value_into_vec() {
use boa_engine::{run_test_actions, TestAction};
Expand Down

0 comments on commit 5b1621a

Please sign in to comment.