boa_engine/value/conversions/
try_into_js.rs1use crate::{Context, JsNativeError, JsResult, JsString, JsValue};
2
3pub trait TryIntoJs: Sized {
5 fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue>;
7}
8
9impl TryIntoJs for bool {
10 fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
11 Ok(JsValue::Boolean(*self))
12 }
13}
14
15impl TryIntoJs for &str {
16 fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
17 Ok(JsValue::String(JsString::from(*self)))
18 }
19}
20impl TryIntoJs for String {
21 fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
22 Ok(JsValue::String(JsString::from(self.as_str())))
23 }
24}
25
26macro_rules! impl_try_into_js_by_from {
27 ($t:ty) => {
28 impl TryIntoJs for $t {
29 fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
30 Ok(JsValue::from(self.clone()))
31 }
32 }
33 };
34 [$($ts:ty),+] => {
35 $(impl_try_into_js_by_from!($ts);)+
36 }
37}
38impl_try_into_js_by_from![i8, u8, i16, u16, i32, u32, f32, f64];
39impl_try_into_js_by_from![
40 JsValue,
41 JsString,
42 crate::JsBigInt,
43 crate::JsObject,
44 crate::JsSymbol,
45 crate::object::JsArray,
46 crate::object::JsArrayBuffer,
47 crate::object::JsDataView,
48 crate::object::JsDate,
49 crate::object::JsFunction,
50 crate::object::JsGenerator,
51 crate::object::JsMapIterator,
52 crate::object::JsMap,
53 crate::object::JsSetIterator,
54 crate::object::JsSet,
55 crate::object::JsSharedArrayBuffer,
56 crate::object::JsInt8Array,
57 crate::object::JsInt16Array,
58 crate::object::JsInt32Array,
59 crate::object::JsUint8Array,
60 crate::object::JsUint16Array,
61 crate::object::JsUint32Array,
62 crate::object::JsFloat32Array,
63 crate::object::JsFloat64Array
64];
65
66const MAX_SAFE_INTEGER_I64: i64 = (1 << 53) - 1;
67const MIN_SAFE_INTEGER_I64: i64 = -MAX_SAFE_INTEGER_I64;
68
69fn err_outside_safe_range() -> crate::JsError {
70 JsNativeError::typ()
71 .with_message("cannot convert value into JsValue: the value is outside the safe range")
72 .into()
73}
74fn convert_safe_i64(value: i64) -> JsValue {
75 i32::try_from(value).map_or(JsValue::Rational(value as f64), JsValue::Integer)
76}
77
78impl TryIntoJs for i64 {
79 fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
80 let value = *self;
81 #[allow(clippy::manual_range_contains)]
82 if value < MIN_SAFE_INTEGER_I64 || MAX_SAFE_INTEGER_I64 < value {
83 Err(err_outside_safe_range())
84 } else {
85 Ok(convert_safe_i64(value))
86 }
87 }
88}
89impl TryIntoJs for u64 {
90 fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
91 let value = *self;
92 if (MAX_SAFE_INTEGER_I64 as u64) < value {
93 Err(err_outside_safe_range())
94 } else {
95 Ok(convert_safe_i64(value as i64))
96 }
97 }
98}
99impl TryIntoJs for i128 {
100 fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
101 let value = *self;
102 if value < i128::from(MIN_SAFE_INTEGER_I64) || i128::from(MAX_SAFE_INTEGER_I64) < value {
103 Err(err_outside_safe_range())
104 } else {
105 Ok(convert_safe_i64(value as i64))
106 }
107 }
108}
109impl TryIntoJs for u128 {
110 fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
111 let value = *self;
112 if (MAX_SAFE_INTEGER_I64 as u128) < value {
113 Err(err_outside_safe_range())
114 } else {
115 Ok(convert_safe_i64(value as i64))
116 }
117 }
118}
119
120impl<T> TryIntoJs for Option<T>
121where
122 T: TryIntoJs,
123{
124 fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> {
125 match self {
126 Some(x) => x.try_into_js(context),
127 None => Ok(JsValue::Undefined),
128 }
129 }
130}
131
132impl<T> TryIntoJs for Vec<T>
133where
134 T: TryIntoJs,
135{
136 fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> {
137 let arr = crate::object::JsArray::new(context);
138 for value in self {
139 let value = value.try_into_js(context)?;
140 arr.push(value, context)?;
141 }
142 Ok(arr.into())
143 }
144}
145
146macro_rules! impl_try_into_js_for_tuples {
147 ($($names:ident : $ts:ident),+) => {
148 impl<$($ts: TryIntoJs,)+> TryIntoJs for ($($ts,)+) {
149 fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> {
150 let ($($names,)+) = self;
151 let arr = crate::object::JsArray::new(context);
152 $(arr.push($names.try_into_js(context)?, context)?;)+
153 Ok(arr.into())
154 }
155 }
156 };
157}
158
159impl_try_into_js_for_tuples!(a: A);
160impl_try_into_js_for_tuples!(a: A, b: B);
161impl_try_into_js_for_tuples!(a: A, b: B, c: C);
162impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D);
163impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E);
164impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F);
165impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G);
166impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H);
167impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I);
168impl_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);
169impl_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);
170
171impl TryIntoJs for () {
172 fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
173 Ok(JsValue::Null)
174 }
175}
176
177impl<T, S> TryIntoJs for std::collections::HashSet<T, S>
178where
179 T: TryIntoJs,
180{
181 fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> {
182 let set = crate::object::JsSet::new(context);
183 for value in self {
184 let value = value.try_into_js(context)?;
185 set.add(value, context)?;
186 }
187 Ok(set.into())
188 }
189}
190
191impl<K, V, S> TryIntoJs for std::collections::HashMap<K, V, S>
192where
193 K: TryIntoJs,
194 V: TryIntoJs,
195{
196 fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> {
197 let map = crate::object::JsMap::new(context);
198 for (key, value) in self {
199 let key = key.try_into_js(context)?;
200 let value = value.try_into_js(context)?;
201 map.set(key, value, context)?;
202 }
203 Ok(map.into())
204 }
205}
206
207#[cfg(test)]
208mod try_into_js_tests {
209 use crate::value::{TryFromJs, TryIntoJs};
210 use crate::{Context, JsResult};
211
212 #[test]
213 fn big_int_err() {
214 fn assert<T: TryIntoJs>(int: &T, context: &mut Context) {
215 let expect_err = int.try_into_js(context);
216 assert!(expect_err.is_err());
217 }
218
219 let mut context = Context::default();
220 let context = &mut context;
221
222 let int = (1 << 55) + 17i64;
223 assert(&int, context);
224
225 let int = (1 << 55) + 17u64;
226 assert(&int, context);
227
228 let int = (1 << 55) + 17u128;
229 assert(&int, context);
230
231 let int = (1 << 55) + 17i128;
232 assert(&int, context);
233 }
234
235 #[test]
236 fn int_tuple() -> JsResult<()> {
237 let mut context = Context::default();
238 let context = &mut context;
239
240 let tuple_initial = (
241 -42i8,
242 42u8,
243 1764i16,
244 7641u16,
245 -((1 << 27) + 13),
246 (1 << 27) + 72u32,
247 (1 << 49) + 1793i64,
248 (1 << 49) + 1793u64,
249 -((1 << 49) + 7193i128),
250 (1 << 49) + 9173u128,
251 );
252
253 #[allow(unused_assignments)]
255 let mut tuple_after_transform = tuple_initial;
256
257 let js_value = tuple_initial.try_into_js(context)?;
258 tuple_after_transform = TryFromJs::try_from_js(&js_value, context)?;
259
260 assert_eq!(tuple_initial, tuple_after_transform);
261 Ok(())
262 }
263
264 #[test]
265 fn string() -> JsResult<()> {
266 let mut context = Context::default();
267 let context = &mut context;
268
269 let s_init = "String".to_string();
270 let js_value = s_init.try_into_js(context)?;
271 let s: String = TryFromJs::try_from_js(&js_value, context)?;
272 assert_eq!(s_init, s);
273 Ok(())
274 }
275
276 #[test]
277 fn vec() -> JsResult<()> {
278 let mut context = Context::default();
279 let context = &mut context;
280
281 let vec_init = vec![(-4i64, 2u64), (15, 15), (32, 23)];
282 let js_value = vec_init.try_into_js(context)?;
283 println!("JsValue: {}", js_value.display());
284 let vec: Vec<(i64, u64)> = TryFromJs::try_from_js(&js_value, context)?;
285 assert_eq!(vec_init, vec);
286 Ok(())
287 }
288}