diff --git a/core/engine/src/value/conversions/try_from_js.rs b/core/engine/src/value/conversions/try_from_js.rs index dc860a3349..955b57f723 100644 --- a/core/engine/src/value/conversions/try_from_js.rs +++ b/core/engine/src/value/conversions/try_from_js.rs @@ -5,6 +5,7 @@ use num_bigint::BigInt; use crate::{js_string, Context, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsValue}; mod collections; +mod tuples; /// This trait adds a fallible and efficient conversions from a [`JsValue`] to Rust types. pub trait TryFromJs: Sized { @@ -367,6 +368,56 @@ fn value_into_vec() { ]); } +#[test] +fn value_into_tuple() { + use boa_engine::{run_test_actions, TestAction}; + use indoc::indoc; + + run_test_actions([ + TestAction::assert_with_op(indoc! {r#" [42, "hello", true] "#}, |value, context| { + type TestType = (i32, String, bool); + TestType::try_from_js(&value, context).unwrap() == (42, "hello".to_string(), true) + }), + TestAction::assert_with_op(indoc! {r#" [42, "hello", true] "#}, |value, context| { + type TestType = (i32, String, Option, Option); + TestType::try_from_js(&value, context).unwrap() + == (42, "hello".to_string(), Some(true), None) + }), + TestAction::assert_with_op(indoc! {r#" [] "#}, |value, context| { + type TestType = ( + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + ); + TestType::try_from_js(&value, context).unwrap() + == (None, None, None, None, None, None, None, None, None, None) + }), + TestAction::assert_with_op(indoc!(r#"[42, "hello", {}]"#), |value, context| { + type TestType = (i32, String, bool); + let Err(value) = TestType::try_from_js(&value, context) else { + return false; + }; + assert!(value.to_string().contains("TypeError")); + true + }), + TestAction::assert_with_op(indoc!(r#"[42, "hello"]"#), |value, context| { + type TestType = (i32, String, bool); + let Err(value) = TestType::try_from_js(&value, context) else { + return false; + }; + assert!(value.to_string().contains("TypeError")); + true + }), + ]); +} + #[test] fn value_into_map() { use boa_engine::{run_test_actions, TestAction}; diff --git a/core/engine/src/value/conversions/try_from_js/tuples.rs b/core/engine/src/value/conversions/try_from_js/tuples.rs new file mode 100644 index 0000000000..babb54a36c --- /dev/null +++ b/core/engine/src/value/conversions/try_from_js/tuples.rs @@ -0,0 +1,41 @@ +//! Implementation of [`TryFromJs`] for tuples. +//! +//! Tuples are converted from a JavaScript array, using similar semantics to `TypeScript` tuples: +//! - If the tuple is shorter than the array, the extra elements are ignored. +//! - If the tuple is longer than the array, the extra elements are `undefined`. +//! - If the array is empty, all elements are `undefined`. +//! +//! A tuple of size 0 (unit type) does not implement [`TryFromJs`]. + +use crate::value::JsValue; +use crate::{Context, JsResult}; + +use super::TryFromJs; + +macro_rules! impl_try_from_js_for_tuples { + ($($name:ident),*) => { + impl<$($name: TryFromJs),*> TryFromJs for ($($name,)*) { + fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { + let vec: Vec = value.try_js_into(context)?; + let mut iter = vec.into_iter(); + + Ok(( + $( + $name::try_from_js(&iter.next().unwrap_or_else(JsValue::undefined), context)?, + )* + )) + } + } + }; +} + +impl_try_from_js_for_tuples!(A); +impl_try_from_js_for_tuples!(A, B); +impl_try_from_js_for_tuples!(A, B, C); +impl_try_from_js_for_tuples!(A, B, C, D); +impl_try_from_js_for_tuples!(A, B, C, D, E); +impl_try_from_js_for_tuples!(A, B, C, D, E, F); +impl_try_from_js_for_tuples!(A, B, C, D, E, F, G); +impl_try_from_js_for_tuples!(A, B, C, D, E, F, G, H); +impl_try_from_js_for_tuples!(A, B, C, D, E, F, G, H, I); +impl_try_from_js_for_tuples!(A, B, C, D, E, F, G, H, I, J);