mirror of https://github.com/boa-dev/boa.git
Browse Source
* #3874: `TryIntoJs` impl for primitive types * #3874: `#[derive(TryIntoJs)]` is it ok to use `create_data_property_or_throw`? in other words, am I create an object correctly? * #3874: some (but not enough) tests * #3874: fix `TryintoJs` derive bug in multi attr case * #3874: `TryIntoJs` derive macro example * fix paths in derive macro * make lint happypull/4025/head
Nikita-str
1 month ago
committed by
GitHub
5 changed files with 496 additions and 2 deletions
@ -0,0 +1,289 @@
|
||||
use crate::{Context, JsNativeError, JsResult, JsValue}; |
||||
use boa_string::JsString; |
||||
|
||||
/// This trait adds a conversions from a Rust Type into [`JsValue`].
|
||||
pub trait TryIntoJs: Sized { |
||||
/// This function tries to convert a `Self` into [`JsValue`].
|
||||
fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue>; |
||||
} |
||||
|
||||
impl TryIntoJs for bool { |
||||
fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> { |
||||
JsResult::Ok(JsValue::Boolean(*self)) |
||||
} |
||||
} |
||||
|
||||
impl TryIntoJs for &str { |
||||
fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> { |
||||
JsResult::Ok(JsValue::String(JsString::from(*self))) |
||||
} |
||||
} |
||||
impl TryIntoJs for String { |
||||
fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> { |
||||
JsResult::Ok(JsValue::String(JsString::from(self.as_str()))) |
||||
} |
||||
} |
||||
|
||||
macro_rules! impl_try_into_js_by_from { |
||||
($t:ty) => { |
||||
impl TryIntoJs for $t { |
||||
fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> { |
||||
JsResult::Ok(JsValue::from(self.clone())) |
||||
} |
||||
} |
||||
}; |
||||
[$($ts:ty),+] => { |
||||
$(impl_try_into_js_by_from!($ts);)+ |
||||
} |
||||
} |
||||
impl_try_into_js_by_from![i8, u8, i16, u16, i32, u32, f32, f64]; |
||||
impl_try_into_js_by_from![ |
||||
JsValue, |
||||
JsString, |
||||
crate::JsBigInt, |
||||
crate::JsObject, |
||||
crate::JsSymbol, |
||||
crate::object::JsArray, |
||||
crate::object::JsArrayBuffer, |
||||
crate::object::JsDataView, |
||||
crate::object::JsDate, |
||||
crate::object::JsFunction, |
||||
crate::object::JsGenerator, |
||||
crate::object::JsMapIterator, |
||||
crate::object::JsMap, |
||||
crate::object::JsSetIterator, |
||||
crate::object::JsSet, |
||||
crate::object::JsSharedArrayBuffer, |
||||
crate::object::JsInt8Array, |
||||
crate::object::JsInt16Array, |
||||
crate::object::JsInt32Array, |
||||
crate::object::JsUint8Array, |
||||
crate::object::JsUint16Array, |
||||
crate::object::JsUint32Array, |
||||
crate::object::JsFloat32Array, |
||||
crate::object::JsFloat64Array |
||||
]; |
||||
|
||||
const MAX_SAFE_INTEGER_I64: i64 = (1 << 53) - 1; |
||||
const MIN_SAFE_INTEGER_I64: i64 = -MAX_SAFE_INTEGER_I64; |
||||
|
||||
fn err_outside_safe_range() -> crate::JsError { |
||||
JsNativeError::typ() |
||||
.with_message("cannot convert value into JsValue: the value is outside the safe range") |
||||
.into() |
||||
} |
||||
fn convert_safe_i64(value: i64) -> JsValue { |
||||
i32::try_from(value).map_or(JsValue::Rational(value as f64), JsValue::Integer) |
||||
} |
||||
|
||||
impl TryIntoJs for i64 { |
||||
fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> { |
||||
let value = *self; |
||||
#[allow(clippy::manual_range_contains)] |
||||
if value < MIN_SAFE_INTEGER_I64 || MAX_SAFE_INTEGER_I64 < value { |
||||
JsResult::Err(err_outside_safe_range()) |
||||
} else { |
||||
JsResult::Ok(convert_safe_i64(value)) |
||||
} |
||||
} |
||||
} |
||||
impl TryIntoJs for u64 { |
||||
fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> { |
||||
let value = *self; |
||||
if (MAX_SAFE_INTEGER_I64 as u64) < value { |
||||
JsResult::Err(err_outside_safe_range()) |
||||
} else { |
||||
JsResult::Ok(convert_safe_i64(value as i64)) |
||||
} |
||||
} |
||||
} |
||||
impl TryIntoJs for i128 { |
||||
fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> { |
||||
let value = *self; |
||||
if value < i128::from(MIN_SAFE_INTEGER_I64) || i128::from(MAX_SAFE_INTEGER_I64) < value { |
||||
JsResult::Err(err_outside_safe_range()) |
||||
} else { |
||||
JsResult::Ok(convert_safe_i64(value as i64)) |
||||
} |
||||
} |
||||
} |
||||
impl TryIntoJs for u128 { |
||||
fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> { |
||||
let value = *self; |
||||
if (MAX_SAFE_INTEGER_I64 as u128) < value { |
||||
JsResult::Err(err_outside_safe_range()) |
||||
} else { |
||||
JsResult::Ok(convert_safe_i64(value as i64)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl<T> TryIntoJs for Option<T> |
||||
where |
||||
T: TryIntoJs, |
||||
{ |
||||
fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> { |
||||
match self { |
||||
Some(x) => x.try_into_js(context), |
||||
None => JsResult::Ok(JsValue::Null), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl<T> TryIntoJs for Vec<T> |
||||
where |
||||
T: TryIntoJs, |
||||
{ |
||||
fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> { |
||||
let arr = crate::object::JsArray::new(context); |
||||
for value in self { |
||||
let value = value.try_into_js(context)?; |
||||
arr.push(value, context)?; |
||||
} |
||||
JsResult::Ok(arr.into()) |
||||
} |
||||
} |
||||
|
||||
macro_rules! impl_try_into_js_for_tuples { |
||||
($($names:ident : $ts:ident),+) => { |
||||
impl<$($ts: TryIntoJs,)+> TryIntoJs for ($($ts,)+) { |
||||
fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> { |
||||
let ($($names,)+) = self; |
||||
let arr = crate::object::JsArray::new(context); |
||||
$(arr.push($names.try_into_js(context)?, context)?;)+ |
||||
JsResult::Ok(arr.into()) |
||||
} |
||||
} |
||||
}; |
||||
} |
||||
|
||||
impl_try_into_js_for_tuples!(a: A); |
||||
impl_try_into_js_for_tuples!(a: A, b: B); |
||||
impl_try_into_js_for_tuples!(a: A, b: B, c: C); |
||||
impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D); |
||||
impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E); |
||||
impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F); |
||||
impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G); |
||||
impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H); |
||||
impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I); |
||||
impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J); |
||||
impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K); |
||||
|
||||
impl TryIntoJs for () { |
||||
fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> { |
||||
JsResult::Ok(JsValue::Null) |
||||
} |
||||
} |
||||
|
||||
impl<T, S> TryIntoJs for std::collections::HashSet<T, S> |
||||
where |
||||
T: TryIntoJs, |
||||
{ |
||||
fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> { |
||||
let set = crate::object::JsSet::new(context); |
||||
for value in self { |
||||
let value = value.try_into_js(context)?; |
||||
set.add(value, context)?; |
||||
} |
||||
JsResult::Ok(set.into()) |
||||
} |
||||
} |
||||
|
||||
impl<K, V, S> TryIntoJs for std::collections::HashMap<K, V, S> |
||||
where |
||||
K: TryIntoJs, |
||||
V: TryIntoJs, |
||||
{ |
||||
fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> { |
||||
let map = crate::object::JsMap::new(context); |
||||
for (key, value) in self { |
||||
let key = key.try_into_js(context)?; |
||||
let value = value.try_into_js(context)?; |
||||
map.set(key, value, context)?; |
||||
} |
||||
JsResult::Ok(map.into()) |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod try_into_js_tests { |
||||
use crate::value::{TryFromJs, TryIntoJs}; |
||||
use crate::{Context, JsResult}; |
||||
|
||||
#[test] |
||||
fn big_int_err() { |
||||
fn assert<T: TryIntoJs>(int: &T, context: &mut Context) { |
||||
let expect_err = int.try_into_js(context); |
||||
assert!(expect_err.is_err()); |
||||
} |
||||
|
||||
let mut context = Context::default(); |
||||
let context = &mut context; |
||||
|
||||
let int = (1 << 55) + 17i64; |
||||
assert(&int, context); |
||||
|
||||
let int = (1 << 55) + 17u64; |
||||
assert(&int, context); |
||||
|
||||
let int = (1 << 55) + 17u128; |
||||
assert(&int, context); |
||||
|
||||
let int = (1 << 55) + 17i128; |
||||
assert(&int, context); |
||||
} |
||||
|
||||
#[test] |
||||
fn int_tuple() -> JsResult<()> { |
||||
let mut context = Context::default(); |
||||
let context = &mut context; |
||||
|
||||
let tuple_initial = ( |
||||
-42i8, |
||||
42u8, |
||||
1764i16, |
||||
7641u16, |
||||
-((1 << 27) + 13), |
||||
(1 << 27) + 72u32, |
||||
(1 << 49) + 1793i64, |
||||
(1 << 49) + 1793u64, |
||||
-((1 << 49) + 7193i128), |
||||
(1 << 49) + 9173u128, |
||||
); |
||||
|
||||
// it will rewrite without reading, so it's just for auto type resolving.
|
||||
#[allow(unused_assignments)] |
||||
let mut tuple_after_transform = tuple_initial; |
||||
|
||||
let js_value = tuple_initial.try_into_js(context)?; |
||||
tuple_after_transform = TryFromJs::try_from_js(&js_value, context)?; |
||||
|
||||
assert_eq!(tuple_initial, tuple_after_transform); |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
fn string() -> JsResult<()> { |
||||
let mut context = Context::default(); |
||||
let context = &mut context; |
||||
|
||||
let s_init = "String".to_string(); |
||||
let js_value = s_init.try_into_js(context)?; |
||||
let s: String = TryFromJs::try_from_js(&js_value, context)?; |
||||
assert_eq!(s_init, s); |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
fn vec() -> JsResult<()> { |
||||
let mut context = Context::default(); |
||||
let context = &mut context; |
||||
|
||||
let vec_init = vec![(-4i64, 2u64), (15, 15), (32, 23)]; |
||||
let js_value = vec_init.try_into_js(context)?; |
||||
println!("JsValue: {}", js_value.display()); |
||||
let vec: Vec<(i64, u64)> = TryFromJs::try_from_js(&js_value, context)?; |
||||
assert_eq!(vec_init, vec); |
||||
Ok(()) |
||||
} |
||||
} |
@ -0,0 +1,96 @@
|
||||
use boa_engine::{ |
||||
js_string, |
||||
value::{TryFromJs, TryIntoJs}, |
||||
Context, JsResult, JsValue, Source, |
||||
}; |
||||
|
||||
#[derive(TryIntoJs)] |
||||
struct Test { |
||||
x: i32, |
||||
#[boa(rename = "y")] |
||||
y_point: i32, |
||||
#[allow(unused)] |
||||
#[boa(skip)] |
||||
tuple: (i32, u8, String), |
||||
#[boa(rename = "isReadable")] |
||||
#[boa(into_js_with = "readable_into_js")] |
||||
is_readable: i8, |
||||
} |
||||
|
||||
#[derive(TryFromJs, Debug, PartialEq, Eq)] |
||||
struct ResultVerifier { |
||||
x: i32, |
||||
y: i32, |
||||
#[boa(rename = "isReadable")] |
||||
is_readable: bool, |
||||
} |
||||
|
||||
fn main() -> JsResult<()> { |
||||
let js_code = r#" |
||||
function pointShift(pointA, pointB) { |
||||
if (pointA.isReadable === true && pointB.isReadable === true) { |
||||
return { |
||||
x: pointA.x + pointB.x, |
||||
y: pointA.y + pointB.y, |
||||
isReadable: true, |
||||
} |
||||
} |
||||
return undefined |
||||
} |
||||
"#; |
||||
|
||||
let mut context = Context::default(); |
||||
let context = &mut context; |
||||
|
||||
context.eval(Source::from_bytes(js_code))?; |
||||
|
||||
let point_shift = context |
||||
.global_object() |
||||
.get(js_string!("pointShift"), context)?; |
||||
let point_shift = point_shift.as_callable().unwrap(); |
||||
|
||||
let a = Test { |
||||
x: 10, |
||||
y_point: 20, |
||||
tuple: (30, 40, "no matter".into()), |
||||
is_readable: 1, |
||||
}; |
||||
let b = Test { |
||||
x: 2, |
||||
y_point: 1, |
||||
tuple: (30, 40, "no matter".into()), |
||||
is_readable: 2, |
||||
}; |
||||
let c = Test { |
||||
x: 2, |
||||
y_point: 1, |
||||
tuple: (30, 40, "no matter".into()), |
||||
is_readable: 0, |
||||
}; |
||||
|
||||
let result = point_shift.call( |
||||
&JsValue::Undefined, |
||||
&[a.try_into_js(context)?, b.try_into_js(context)?], |
||||
context, |
||||
)?; |
||||
let verifier = ResultVerifier::try_from_js(&result, context)?; |
||||
let expect = ResultVerifier { |
||||
x: 10 + 2, |
||||
y: 20 + 1, |
||||
is_readable: true, |
||||
}; |
||||
assert_eq!(verifier, expect); |
||||
|
||||
let result = point_shift.call( |
||||
&JsValue::Undefined, |
||||
&[a.try_into_js(context)?, c.try_into_js(context)?], |
||||
context, |
||||
)?; |
||||
assert!(result.is_undefined()); |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
fn readable_into_js(value: &i8, _context: &mut Context) -> JsResult<JsValue> { |
||||
Ok(JsValue::Boolean(*value != 0)) |
||||
} |
Loading…
Reference in new issue