From 69db8ec8b4f77344753e7d583ebc52243a46c080 Mon Sep 17 00:00:00 2001 From: Monika Jakhar Date: Tue, 17 Mar 2026 17:07:27 +0530 Subject: [PATCH 1/3] fix: ensure TryIntoJs has prototype and support wrapper types --- core/engine/src/module/loader/mod.rs | 3 +- .../src/value/conversions/try_from_js.rs | 27 +++++++ .../src/value/conversions/try_into_js.rs | 75 ++++++++++++++++--- core/macros/src/class.rs | 9 +++ 4 files changed, 102 insertions(+), 12 deletions(-) diff --git a/core/engine/src/module/loader/mod.rs b/core/engine/src/module/loader/mod.rs index c38f7b708d7..6f3ba75a5f5 100644 --- a/core/engine/src/module/loader/mod.rs +++ b/core/engine/src/module/loader/mod.rs @@ -3,6 +3,7 @@ use std::cell::RefCell; use std::path::{Component, Path, PathBuf}; use std::rc::Rc; +use cow_utils::CowUtils; use dynify::dynify; use rustc_hash::FxHashMap; @@ -62,7 +63,7 @@ pub fn resolve_module_specifier( // On Windows, also replace `/` with `\`. JavaScript imports use `/` as path separator. #[cfg(target_family = "windows")] - let specifier = specifier.replace('/', "\\"); + let specifier = specifier.cow_replace('/', "\\").into_owned(); let short_path = Path::new(&specifier); diff --git a/core/engine/src/value/conversions/try_from_js.rs b/core/engine/src/value/conversions/try_from_js.rs index 093356c89a2..fe947765de8 100644 --- a/core/engine/src/value/conversions/try_from_js.rs +++ b/core/engine/src/value/conversions/try_from_js.rs @@ -71,6 +71,33 @@ impl TryFromJs for JsString { } } +impl TryFromJs for Box +where + T: TryFromJs, +{ + fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { + T::try_from_js(value, context).map(Box::new) + } +} + +impl TryFromJs for std::rc::Rc +where + T: TryFromJs, +{ + fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { + T::try_from_js(value, context).map(std::rc::Rc::new) + } +} + +impl TryFromJs for std::sync::Arc +where + T: TryFromJs, +{ + fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { + T::try_from_js(value, context).map(std::sync::Arc::new) + } +} + impl TryFromJs for Option where T: TryFromJs, diff --git a/core/engine/src/value/conversions/try_into_js.rs b/core/engine/src/value/conversions/try_into_js.rs index 585033f17bd..ad0a83b4e52 100644 --- a/core/engine/src/value/conversions/try_into_js.rs +++ b/core/engine/src/value/conversions/try_into_js.rs @@ -1,4 +1,3 @@ -use crate::class::Class; use crate::{Context, JsNativeError, JsResult, JsString, JsValue}; /// This trait adds a conversions from a Rust Type into [`JsValue`]. @@ -7,15 +6,6 @@ pub trait TryIntoJs: Sized { fn try_into_js(&self, context: &mut Context) -> JsResult; } -impl TryIntoJs for T -where - T: Class + Clone, -{ - fn try_into_js(&self, context: &mut Context) -> JsResult { - T::from_data(self.clone(), context).map(JsValue::from) - } -} - impl TryIntoJs for bool { fn try_into_js(&self, _context: &mut Context) -> JsResult { Ok(JsValue::from(*self)) @@ -146,6 +136,42 @@ impl TryIntoJs for u128 { } } +impl TryIntoJs for &T +where + T: TryIntoJs, +{ + fn try_into_js(&self, context: &mut Context) -> JsResult { + (**self).try_into_js(context) + } +} + +impl TryIntoJs for Box +where + T: TryIntoJs, +{ + fn try_into_js(&self, context: &mut Context) -> JsResult { + self.as_ref().try_into_js(context) + } +} + +impl TryIntoJs for std::rc::Rc +where + T: TryIntoJs, +{ + fn try_into_js(&self, context: &mut Context) -> JsResult { + self.as_ref().try_into_js(context) + } +} + +impl TryIntoJs for std::sync::Arc +where + T: TryIntoJs, +{ + fn try_into_js(&self, context: &mut Context) -> JsResult { + self.as_ref().try_into_js(context) + } +} + impl TryIntoJs for Option where T: TryIntoJs, @@ -236,7 +262,7 @@ where #[cfg(test)] mod try_into_js_tests { use crate::value::{TryFromJs, TryIntoJs}; - use crate::{Context, JsResult}; + use crate::{Context, JsResult, JsValue}; #[test] fn big_int_err() { @@ -314,4 +340,31 @@ mod try_into_js_tests { assert_eq!(vec_init, vec); Ok(()) } + + #[test] + fn manual_repro_4360() -> JsResult<()> { + use crate::JsObject; + use crate::js_string; + + let mut context = Context::default(); + let context = &mut context; + + let obj = JsObject::default(context.intrinsics()); + obj.create_data_property_or_throw( + js_string!("foo"), + TryIntoJs::try_into_js(&0usize, context).unwrap(), + context, + ) + .unwrap(); + obj.create_data_property_or_throw( + js_string!("bar"), + TryIntoJs::try_into_js(&1usize, context).unwrap(), + context, + ) + .unwrap(); + let value: JsValue = obj.into(); + let s = value.to_string(context).unwrap(); + assert_eq!(s.to_std_string_escaped(), "[object Object]"); + Ok(()) + } } diff --git a/core/macros/src/class.rs b/core/macros/src/class.rs index 2a74be2dbc3..580340e8c25 100644 --- a/core/macros/src/class.rs +++ b/core/macros/src/class.rs @@ -635,6 +635,15 @@ impl ClassVisitor { Ok(()) } } + + impl boa_engine::value::TryIntoJs for #class_ty + where + Self: Clone, + { + fn try_into_js(&self, context: &mut boa_engine::Context) -> boa_engine::JsResult { + ::from_data(self.clone(), context).map(Into::into) + } + } } } } From 6c59e29bff7a6ff32301c11f4e9ed140192c8a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Juli=C3=A1n=20Espina?= Date: Wed, 18 Mar 2026 08:21:06 -0600 Subject: [PATCH 2/3] Apply lints --- core/engine/src/module/loader/mod.rs | 3 +-- core/engine/src/value/conversions/try_into_js.rs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/engine/src/module/loader/mod.rs b/core/engine/src/module/loader/mod.rs index 6f3ba75a5f5..bafb778a53f 100644 --- a/core/engine/src/module/loader/mod.rs +++ b/core/engine/src/module/loader/mod.rs @@ -3,7 +3,6 @@ use std::cell::RefCell; use std::path::{Component, Path, PathBuf}; use std::rc::Rc; -use cow_utils::CowUtils; use dynify::dynify; use rustc_hash::FxHashMap; @@ -63,7 +62,7 @@ pub fn resolve_module_specifier( // On Windows, also replace `/` with `\`. JavaScript imports use `/` as path separator. #[cfg(target_family = "windows")] - let specifier = specifier.cow_replace('/', "\\").into_owned(); + let specifier = CowUtils::cow_replace(specifier, '/', "\\"); let short_path = Path::new(&specifier); diff --git a/core/engine/src/value/conversions/try_into_js.rs b/core/engine/src/value/conversions/try_into_js.rs index ad0a83b4e52..a20d8168b60 100644 --- a/core/engine/src/value/conversions/try_into_js.rs +++ b/core/engine/src/value/conversions/try_into_js.rs @@ -341,8 +341,9 @@ mod try_into_js_tests { Ok(()) } + // https://github.com/boa-dev/boa/issues/4360 #[test] - fn manual_repro_4360() -> JsResult<()> { + fn try_into_js_to_string() { use crate::JsObject; use crate::js_string; @@ -365,6 +366,5 @@ mod try_into_js_tests { let value: JsValue = obj.into(); let s = value.to_string(context).unwrap(); assert_eq!(s.to_std_string_escaped(), "[object Object]"); - Ok(()) } } From 4721a96d8d2c1fece6add7cf0cc64211a6d64ce5 Mon Sep 17 00:00:00 2001 From: Monika Jakhar Date: Wed, 18 Mar 2026 21:32:40 +0530 Subject: [PATCH 3/3] fix: resolve compilation error in cow_replace usage on Windows Use `&*specifier` to deref Cow for Path::new and join calls, and use the full path `cow_utils::CowUtils::cow_replace` without import. --- core/engine/src/module/loader/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/engine/src/module/loader/mod.rs b/core/engine/src/module/loader/mod.rs index bafb778a53f..be8d7929178 100644 --- a/core/engine/src/module/loader/mod.rs +++ b/core/engine/src/module/loader/mod.rs @@ -62,9 +62,9 @@ pub fn resolve_module_specifier( // On Windows, also replace `/` with `\`. JavaScript imports use `/` as path separator. #[cfg(target_family = "windows")] - let specifier = CowUtils::cow_replace(specifier, '/', "\\"); + let specifier = cow_utils::CowUtils::cow_replace(specifier.as_str(), '/', "\\"); - let short_path = Path::new(&specifier); + let short_path = Path::new(&*specifier); // In ECMAScript, a path is considered relative if it starts with // `./` or `../`. In Rust it's any path that start with `/`. @@ -79,7 +79,7 @@ pub fn resolve_module_specifier( )); } } else { - base_path.join(&specifier) + base_path.join(&*specifier) }; if long_path.is_relative() && base.is_some() {