From bfb3770b26d991effc88701a271d86b600e71fda Mon Sep 17 00:00:00 2001 From: Billy Bradley Date: Tue, 30 Nov 2021 11:19:48 +0000 Subject: [PATCH 1/7] Enable passing String vectors and boxed slices across ABI This is accomplished via conversion of the Strings to/from JsValues. --- src/convert/slices.rs | 123 +++++++++++++++++++++++++++++++++++++++++- src/describe.rs | 11 +++- 2 files changed, 131 insertions(+), 3 deletions(-) diff --git a/src/convert/slices.rs b/src/convert/slices.rs index 9d0970f4e6a..1075aab1cde 100644 --- a/src/convert/slices.rs +++ b/src/convert/slices.rs @@ -1,3 +1,4 @@ +use std::convert::{TryFrom, TryInto}; #[cfg(feature = "std")] use std::prelude::v1::*; @@ -7,6 +8,8 @@ use core::str; use crate::cast::JsObject; use crate::convert::OptionIntoWasmAbi; use crate::convert::{FromWasmAbi, IntoWasmAbi, RefFromWasmAbi, RefMutFromWasmAbi, WasmAbi}; +use crate::describe; +use crate::describe::WasmDescribe; use cfg_if::cfg_if; if_std! { @@ -27,9 +30,18 @@ fn null_slice() -> WasmSlice { WasmSlice { ptr: 0, len: 0 } } +pub trait BasicType: WasmDescribe {} + macro_rules! vectors { ($($t:ident)*) => ($( if_std! { + impl WasmDescribe for Box<[$t]> { + fn describe() { + describe::inform(describe::VECTOR); + $t::describe(); + } + } + impl IntoWasmAbi for Box<[$t]> { type Abi = WasmSlice; @@ -104,7 +116,7 @@ macro_rules! vectors { #[inline] unsafe fn ref_from_abi(js: WasmSlice) -> Box<[$t]> { - >::from_abi(js) + as FromWasmAbi>::from_abi(js) } } @@ -129,6 +141,115 @@ vectors! { u8 i8 u16 i16 u32 i32 u64 i64 usize isize f32 f64 } +/// Enables blanket implementations of `WasmDescribe`, `IntoWasmAbi`, +/// `FromWasmAbi` and `OptionIntoWasmAbi` functionality on boxed slices of +/// types which can be converted to and from `JsValue` without conflicting +/// implementations of those traits. +/// +/// Implementing these traits directly with blanket implementations would +/// be much more elegant, but unfortunately that's impossible because it +/// conflicts with the implementations for `Box<[T]> where T: JsObject`. +pub trait JsValueVector { + type ToAbi; + type FromAbi; + + fn describe(); + fn into_abi(self) -> Self::ToAbi; + fn none() -> Self::ToAbi; + unsafe fn from_abi(js: Self::FromAbi) -> Self; +} + +/* + * Generates implementations for traits necessary for passing types to and from + * JavaScript on boxed slices of values which can be converted to and from + * `JsValue`. + */ +macro_rules! js_value_vectors { + ($($t:ident)*) => ($( + if_std! { + impl WasmDescribe for Box<[$t]> { + fn describe() { + ::describe(); + } + } + + impl IntoWasmAbi for Box<[$t]> { + type Abi = ::ToAbi; + + fn into_abi(self) -> Self::Abi { + ::into_abi(self) + } + } + + impl OptionIntoWasmAbi for Box<[$t]> { + fn none() -> Self::Abi { + ::none() + } + } + + impl FromWasmAbi for Box<[$t]> { + type Abi = ::FromAbi; + + unsafe fn from_abi(js: Self::Abi) -> Self { + ::from_abi(js) + } + } + } + )*) +} + +if_std! { + impl JsValueVector for Box<[T]> where + T: Into + TryFrom, + >::Error: core::fmt::Debug { + type ToAbi = as IntoWasmAbi>::Abi; + type FromAbi = as FromWasmAbi>::Abi; + + fn describe() { + describe::inform(describe::VECTOR); + JsValue::describe(); + } + + fn into_abi(self) -> Self::ToAbi { + let js_vals: Box::<[JsValue]> = self + .into_vec() + .into_iter() + .map(|x| x.into()) + .collect(); + + IntoWasmAbi::into_abi(js_vals) + } + + fn none() -> Self::ToAbi { + as OptionIntoWasmAbi>::none() + } + + unsafe fn from_abi(js: Self::FromAbi) -> Self { + let js_vals = as FromWasmAbi>::from_abi(js); + + js_vals + .into_iter() + .map(|x| x.try_into().expect("Array element of wrong type")) + .collect() + } + } + + impl TryFrom for String { + type Error = (); + + fn try_from(value: JsValue) -> Result { + match value.as_string() { + Some(s) => Ok(s), + None => Err(()), + } + } + } +} + +js_value_vectors! { + String +} + cfg_if! { if #[cfg(feature = "enable-interning")] { #[inline] diff --git a/src/describe.rs b/src/describe.rs index 2d424f8f3f7..88c0eea48a6 100644 --- a/src/describe.rs +++ b/src/describe.rs @@ -3,7 +3,7 @@ #![doc(hidden)] -use crate::{Clamped, JsValue}; +use crate::{Clamped, JsObject, JsValue}; use cfg_if::cfg_if; macro_rules! tys { @@ -143,7 +143,14 @@ if_std! { } } - impl WasmDescribe for Box<[T]> { + impl WasmDescribe for Box<[JsValue]> { + fn describe() { + inform(VECTOR); + JsValue::describe(); + } + } + + impl WasmDescribe for Box<[T]> where T: JsObject { fn describe() { inform(VECTOR); T::describe(); From cba74282c8084e405579744970ccd9b5affe9993 Mon Sep 17 00:00:00 2001 From: Billy Bradley Date: Tue, 7 Dec 2021 13:22:39 +0000 Subject: [PATCH 2/7] Enable passing custom struct vectors over ABI This was done by converting the structs to/from JsValues. It was necessary to change the way relevant traits (e.g. WasmDescribe, IntoWasmAbi etc.) are implemented. It's impossible to implement these for `Box<[#name]>` in codegen.rs because implementing traits on generic types is only allowed in the crate in which the trait is defined. Naively adding a blanket implementation on the wasm-bindgen side doesn't work either because it conflicts with the implementation for JsObjects. The solution was to create traits like VectorIntoWasmAbi etc. that are defined on the concrete type and contain the implementation for IntoWasmAbi etc. for vectors of that type. JsObjects are blanket implemented as before, and struct types are implemented in codegen.rs. Due to the way these traits are defined, Rust requires implementing types to be Sized, so they can't be used for the existing String implementations. Converting structs from JsValues was accomplished by adding an unwrap function to the generated JavaScript class, and calling that from Rust. --- crates/backend/src/codegen.rs | 73 +++++++++ crates/cli-support/src/js/mod.rs | 28 ++++ crates/cli-support/src/wit/mod.rs | 35 ++++- crates/cli-support/src/wit/nonstandard.rs | 5 + crates/cli-support/src/wit/section.rs | 3 + crates/shared/src/lib.rs | 7 + src/convert/impls.rs | 45 ++++++ src/convert/slices.rs | 178 +++++++++------------- src/convert/traits.rs | 43 ++++++ src/describe.rs | 16 +- 10 files changed, 321 insertions(+), 112 deletions(-) diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 7292b3da0d8..7c0f4034fcc 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -145,6 +145,7 @@ impl ToTokens for ast::Struct { let name_chars = name_str.chars().map(|c| c as u32); let new_fn = Ident::new(&shared::new_function(&name_str), Span::call_site()); let free_fn = Ident::new(&shared::free_function(&name_str), Span::call_site()); + let unwrap_fn = Ident::new(&shared::unwrap_function(&name_str), Span::call_site()); (quote! { #[allow(clippy::all)] impl wasm_bindgen::describe::WasmDescribe for #name { @@ -257,6 +258,78 @@ impl ToTokens for ast::Struct { fn is_none(abi: &Self::Abi) -> bool { *abi == 0 } } + #[allow(clippy::all)] + impl wasm_bindgen::__rt::core::convert::TryFrom for #name { + type Error = (); + + fn try_from(value: wasm_bindgen::JsValue) + -> wasm_bindgen::__rt::std::result::Result { + let js_ptr = wasm_bindgen::convert::IntoWasmAbi::into_abi(value); + + #[link(wasm_import_module = "__wbindgen_placeholder__")] + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + extern "C" { + fn #unwrap_fn(ptr: u32) -> u32; + } + + #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] + unsafe fn #unwrap_fn(_: u32) -> u32 { + panic!("cannot convert from JsValue outside of the wasm target") + } + + let ptr; + unsafe { + ptr = #unwrap_fn(js_ptr); + } + + if(ptr == 0) { + wasm_bindgen::__rt::std::result::Result::Err(()) + } else { + unsafe { + wasm_bindgen::__rt::std::result::Result::Ok( + ::from_abi(ptr) + ) + } + } + } + } + + impl wasm_bindgen::describe::WasmDescribeVector for #name { + use wasm_bindgen::__rt::std::boxed::Box; + + fn describe_vector() { + as wasm_bindgen::convert::JsValueVector>::describe(); + } + } + + impl wasm_bindgen::convert::VectorIntoWasmAbi for #name { + use wasm_bindgen::__rt::std::boxed::Box; + + type Abi = as wasm_bindgen::convert::JsValueVector>::ToAbi; + + fn vector_into_abi(vector: Box<[#name]>) -> Self::Abi { + as wasm_bindgen::convert::JsValueVector>::into_abi(vector) + } + } + + impl wasm_bindgen::convert::OptionVectorIntoWasmAbi for #name { + use wasm_bindgen::__rt::std::boxed::Box; + + fn vector_none() -> as wasm_bindgen::convert::JsValueVector>::ToAbi { + as wasm_bindgen::convert::JsValueVector>::none() + } + } + + impl wasm_bindgen::convert::VectorFromWasmAbi for #name { + use wasm_bindgen::__rt::std::boxed::Box; + + type Abi = as wasm_bindgen::convert::JsValueVector>::FromAbi; + + unsafe fn vector_from_abi(js: Self::Abi) -> Box<[#name]> { + as wasm_bindgen::convert::JsValueVector>::from_abi(js) + } + } + }) .to_tokens(tokens); diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 724aafbef3e..0828cc72d88 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -65,6 +65,7 @@ pub struct ExportedClass { typescript: String, has_constructor: bool, wrap_needed: bool, + unwrap_needed: bool, /// Whether to generate helper methods for inspecting the class is_inspectable: bool, /// All readable properties of the class @@ -844,6 +845,20 @@ impl<'a> Context<'a> { )); } + if class.unwrap_needed { + dst.push_str(&format!( + " + static __unwrap(jsValue) {{ + if (!(jsValue instanceof {})) {{ + return 0; + }} + return jsValue.__destroy_into_raw(); + }} + ", + name, + )); + } + if self.config.weak_refs { self.global(&format!( "const {}Finalization = new FinalizationRegistry(ptr => wasm.{}(ptr));", @@ -2089,6 +2104,10 @@ impl<'a> Context<'a> { require_class(&mut self.exported_classes, name).wrap_needed = true; } + fn require_class_unwrap(&mut self, name: &str) { + require_class(&mut self.exported_classes, name).unwrap_needed = true; + } + fn add_module_import(&mut self, module: String, name: &str, actual: &str) { let rename = if name == actual { None @@ -2890,6 +2909,15 @@ impl<'a> Context<'a> { assert!(!variadic); self.invoke_intrinsic(intrinsic, args, prelude) } + + AuxImport::UnwrapExportedClass(class) => { + assert!(kind == AdapterJsImportKind::Normal); + assert!(!variadic); + assert_eq!(args.len(), 1); + self.require_class_unwrap(class); + self.expose_take_object(); + Ok(format!("{}.__unwrap({})", class, args[0])) + } } } diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 0bb382b9cfb..eabf42046d5 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -858,17 +858,40 @@ impl<'a> Context<'a> { self.aux.structs.push(aux); let wrap_constructor = wasm_bindgen_shared::new_function(struct_.name); - if let Some((import_id, _id)) = self.function_imports.get(&wrap_constructor).cloned() { + self.add_aux_import_to_import_map( + &wrap_constructor, + vec![Descriptor::I32], + Descriptor::Externref, + AuxImport::WrapInExportedClass(struct_.name.to_string()), + )?; + + let unwrap_fn = wasm_bindgen_shared::unwrap_function(struct_.name); + self.add_aux_import_to_import_map( + &unwrap_fn, + vec![Descriptor::Externref], + Descriptor::I32, + AuxImport::UnwrapExportedClass(struct_.name.to_string()), + )?; + + Ok(()) + } + + fn add_aux_import_to_import_map( + &mut self, + fn_name: &String, + arguments: Vec, + ret: Descriptor, + aux_import: AuxImport, + ) -> Result<(), Error> { + if let Some((import_id, _id)) = self.function_imports.get(fn_name).cloned() { let signature = Function { shim_idx: 0, - arguments: vec![Descriptor::I32], - ret: Descriptor::Externref, + arguments, + ret, inner_ret: None, }; let id = self.import_adapter(import_id, signature, AdapterJsImportKind::Normal)?; - self.aux - .import_map - .insert(id, AuxImport::WrapInExportedClass(struct_.name.to_string())); + self.aux.import_map.insert(id, aux_import); } Ok(()) diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index 35d966f602d..89d6caa4727 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -309,6 +309,11 @@ pub enum AuxImport { /// This is an intrinsic function expected to be implemented with a JS glue /// shim. Each intrinsic has its own expected signature and implementation. Intrinsic(Intrinsic), + + /// This import is a generated shim which will attempt to unwrap JsValue to an + /// instance of the given exported class. The class name is one that is + /// exported from the Rust/wasm. + UnwrapExportedClass(String), } /// Values that can be imported verbatim to hook up to an import. diff --git a/crates/cli-support/src/wit/section.rs b/crates/cli-support/src/wit/section.rs index 55042f12d98..42c3c6a59cc 100644 --- a/crates/cli-support/src/wit/section.rs +++ b/crates/cli-support/src/wit/section.rs @@ -353,6 +353,9 @@ fn check_standard_import(import: &AuxImport) -> Result<(), Error> { format!("wasm-bindgen specific intrinsic `{}`", intrinsic.name()) } AuxImport::Closure { .. } => format!("creating a `Closure` wrapper"), + AuxImport::UnwrapExportedClass(name) => { + format!("unwrapping a pointer from a `{}` js class wrapper", name) + } }; bail!("import of {} requires JS glue", item); } diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 2c2eecd33a8..8a48bb5385b 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -157,6 +157,13 @@ pub fn free_function(struct_name: &str) -> String { return name; } +pub fn unwrap_function(struct_name: &str) -> String { + let mut name = format!("__wbg_"); + name.extend(struct_name.chars().flat_map(|s| s.to_lowercase())); + name.push_str("_unwrap"); + return name; +} + pub fn free_function_export_name(function_name: &str) -> String { function_name.to_string() } diff --git a/src/convert/impls.rs b/src/convert/impls.rs index 89f74dbfa16..a5265d32a18 100644 --- a/src/convert/impls.rs +++ b/src/convert/impls.rs @@ -4,8 +4,16 @@ use core::mem::{self, ManuallyDrop}; use crate::convert::traits::WasmAbi; use crate::convert::{FromWasmAbi, IntoWasmAbi, RefFromWasmAbi}; use crate::convert::{OptionFromWasmAbi, OptionIntoWasmAbi, ReturnWasmAbi}; +use crate::describe::{self, WasmDescribe}; use crate::{Clamped, JsValue}; +if_std! { + use std::boxed::Box; + use std::convert::{TryFrom, TryInto}; + use std::vec::Vec; + use crate::convert::JsValueVector; +} + unsafe impl WasmAbi for () {} #[repr(C)] @@ -407,3 +415,40 @@ impl ReturnWasmAbi for Result { } } } + +if_std! { + impl JsValueVector for Box<[T]> where + T: Into + TryFrom, + >::Error: core::fmt::Debug { + type ToAbi = as IntoWasmAbi>::Abi; + type FromAbi = as FromWasmAbi>::Abi; + + fn describe() { + describe::inform(describe::VECTOR); + JsValue::describe(); + } + + fn into_abi(self) -> Self::ToAbi { + let js_vals: Box::<[JsValue]> = self + .into_vec() + .into_iter() + .map(|x| x.into()) + .collect(); + + IntoWasmAbi::into_abi(js_vals) + } + + fn none() -> Self::ToAbi { + as OptionIntoWasmAbi>::none() + } + + unsafe fn from_abi(js: Self::FromAbi) -> Self { + let js_vals = as FromWasmAbi>::from_abi(js); + + js_vals + .into_iter() + .filter_map(|x| x.try_into().ok()) + .collect() + } + } +} diff --git a/src/convert/slices.rs b/src/convert/slices.rs index 1075aab1cde..c4992a9c496 100644 --- a/src/convert/slices.rs +++ b/src/convert/slices.rs @@ -1,20 +1,18 @@ -use std::convert::{TryFrom, TryInto}; #[cfg(feature = "std")] use std::prelude::v1::*; -use core::slice; -use core::str; +use cfg_if::cfg_if; use crate::cast::JsObject; -use crate::convert::OptionIntoWasmAbi; use crate::convert::{FromWasmAbi, IntoWasmAbi, RefFromWasmAbi, RefMutFromWasmAbi, WasmAbi}; -use crate::describe; -use crate::describe::WasmDescribe; -use cfg_if::cfg_if; +use crate::convert::{OptionVectorFromWasmAbi, OptionVectorIntoWasmAbi}; +use crate::convert::{VectorFromWasmAbi, VectorIntoWasmAbi}; +use crate::describe::{self, WasmDescribe, WasmDescribeVector}; if_std! { - use core::mem; - use crate::convert::OptionFromWasmAbi; + use core::{mem, slice, str}; + use std::convert::TryFrom; + use crate::convert::{OptionFromWasmAbi, OptionIntoWasmAbi, JsValueVector}; } #[repr(C)] @@ -30,26 +28,24 @@ fn null_slice() -> WasmSlice { WasmSlice { ptr: 0, len: 0 } } -pub trait BasicType: WasmDescribe {} - macro_rules! vectors { ($($t:ident)*) => ($( if_std! { - impl WasmDescribe for Box<[$t]> { - fn describe() { + impl WasmDescribeVector for $t { + fn describe_vector() { describe::inform(describe::VECTOR); $t::describe(); } } - impl IntoWasmAbi for Box<[$t]> { + impl VectorIntoWasmAbi for $t { type Abi = WasmSlice; #[inline] - fn into_abi(self) -> WasmSlice { - let ptr = self.as_ptr(); - let len = self.len(); - mem::forget(self); + fn vector_into_abi(vector: Box<[$t]>) -> WasmSlice { + let ptr = vector.as_ptr(); + let len = vector.len(); + mem::forget(vector); WasmSlice { ptr: ptr.into_abi(), len: len as u32, @@ -57,25 +53,25 @@ macro_rules! vectors { } } - impl OptionIntoWasmAbi for Box<[$t]> { + impl OptionVectorIntoWasmAbi for $t { #[inline] - fn none() -> WasmSlice { null_slice() } + fn vector_none() -> WasmSlice { null_slice() } } - impl FromWasmAbi for Box<[$t]> { + impl VectorFromWasmAbi for $t { type Abi = WasmSlice; #[inline] - unsafe fn from_abi(js: WasmSlice) -> Self { + unsafe fn vector_from_abi(js: WasmSlice) -> Box<[$t]> { let ptr = <*mut $t>::from_abi(js.ptr); let len = js.len as usize; Vec::from_raw_parts(ptr, len, len).into_boxed_slice() } } - impl OptionFromWasmAbi for Box<[$t]> { + impl OptionVectorFromWasmAbi for $t { #[inline] - fn is_none(slice: &WasmSlice) -> bool { slice.ptr == 0 } + fn vector_is_none(slice: &WasmSlice) -> bool { slice.ptr == 0 } } } @@ -141,24 +137,6 @@ vectors! { u8 i8 u16 i16 u32 i32 u64 i64 usize isize f32 f64 } -/// Enables blanket implementations of `WasmDescribe`, `IntoWasmAbi`, -/// `FromWasmAbi` and `OptionIntoWasmAbi` functionality on boxed slices of -/// types which can be converted to and from `JsValue` without conflicting -/// implementations of those traits. -/// -/// Implementing these traits directly with blanket implementations would -/// be much more elegant, but unfortunately that's impossible because it -/// conflicts with the implementations for `Box<[T]> where T: JsObject`. -pub trait JsValueVector { - type ToAbi; - type FromAbi; - - fn describe(); - fn into_abi(self) -> Self::ToAbi; - fn none() -> Self::ToAbi; - unsafe fn from_abi(js: Self::FromAbi) -> Self; -} - /* * Generates implementations for traits necessary for passing types to and from * JavaScript on boxed slices of values which can be converted to and from @@ -167,12 +145,13 @@ pub trait JsValueVector { macro_rules! js_value_vectors { ($($t:ident)*) => ($( if_std! { - impl WasmDescribe for Box<[$t]> { - fn describe() { - ::describe(); + impl WasmDescribeVector for $t { + fn describe_vector() { + as JsValueVector>::describe(); } } + // Can't use VectorIntoWasmAbi etc. because $t isn't necessarily Sized impl IntoWasmAbi for Box<[$t]> { type Abi = ::ToAbi; @@ -182,7 +161,7 @@ macro_rules! js_value_vectors { } impl OptionIntoWasmAbi for Box<[$t]> { - fn none() -> Self::Abi { + fn none() -> ::ToAbi { ::none() } } @@ -199,41 +178,6 @@ macro_rules! js_value_vectors { } if_std! { - impl JsValueVector for Box<[T]> where - T: Into + TryFrom, - >::Error: core::fmt::Debug { - type ToAbi = as IntoWasmAbi>::Abi; - type FromAbi = as FromWasmAbi>::Abi; - - fn describe() { - describe::inform(describe::VECTOR); - JsValue::describe(); - } - - fn into_abi(self) -> Self::ToAbi { - let js_vals: Box::<[JsValue]> = self - .into_vec() - .into_iter() - .map(|x| x.into()) - .collect(); - - IntoWasmAbi::into_abi(js_vals) - } - - fn none() -> Self::ToAbi { - as OptionIntoWasmAbi>::none() - } - - unsafe fn from_abi(js: Self::FromAbi) -> Self { - let js_vals = as FromWasmAbi>::from_abi(js); - - js_vals - .into_iter() - .map(|x| x.try_into().expect("Array element of wrong type")) - .collect() - } - } - impl TryFrom for String { type Error = (); @@ -357,14 +301,42 @@ impl RefFromWasmAbi for str { if_std! { use crate::JsValue; - impl IntoWasmAbi for Box<[JsValue]> { + impl IntoWasmAbi for Box<[T]> { + type Abi = ::Abi; + + fn into_abi(self) -> Self::Abi { + T::vector_into_abi(self) + } + } + + impl OptionIntoWasmAbi for Box<[T]> { + fn none() -> ::Abi { + T::vector_none() + } + } + + impl FromWasmAbi for Box<[T]> { + type Abi = ::Abi; + + unsafe fn from_abi(js: Self::Abi) -> Self { + T::vector_from_abi(js) + } + } + + impl OptionFromWasmAbi for Box<[T]> { + fn is_none(slice: &::Abi) -> bool { + T::vector_is_none(slice) + } + } + + impl VectorIntoWasmAbi for JsValue { type Abi = WasmSlice; #[inline] - fn into_abi(self) -> WasmSlice { - let ptr = self.as_ptr(); - let len = self.len(); - mem::forget(self); + fn vector_into_abi(vector: Box<[Self]>) -> WasmSlice { + let ptr = vector.as_ptr(); + let len = vector.len(); + mem::forget(vector); WasmSlice { ptr: ptr.into_abi(), len: len as u32, @@ -372,35 +344,35 @@ if_std! { } } - impl OptionIntoWasmAbi for Box<[JsValue]> { + impl OptionVectorIntoWasmAbi for JsValue { #[inline] - fn none() -> WasmSlice { null_slice() } + fn vector_none() -> WasmSlice { null_slice() } } - impl FromWasmAbi for Box<[JsValue]> { + impl VectorFromWasmAbi for JsValue { type Abi = WasmSlice; #[inline] - unsafe fn from_abi(js: WasmSlice) -> Self { + unsafe fn vector_from_abi(js: WasmSlice) -> Box<[Self]> { let ptr = <*mut JsValue>::from_abi(js.ptr); let len = js.len as usize; Vec::from_raw_parts(ptr, len, len).into_boxed_slice() } } - impl OptionFromWasmAbi for Box<[JsValue]> { + impl OptionVectorFromWasmAbi for JsValue { #[inline] - fn is_none(slice: &WasmSlice) -> bool { slice.ptr == 0 } + fn vector_is_none(slice: &WasmSlice) -> bool { slice.ptr == 0 } } - impl IntoWasmAbi for Box<[T]> where T: JsObject { + impl VectorIntoWasmAbi for T where T: JsObject { type Abi = WasmSlice; #[inline] - fn into_abi(self) -> WasmSlice { - let ptr = self.as_ptr(); - let len = self.len(); - mem::forget(self); + fn vector_into_abi(vector: Box<[T]>) -> WasmSlice { + let ptr = vector.as_ptr(); + let len = vector.len(); + mem::forget(vector); WasmSlice { ptr: ptr.into_abi(), len: len as u32, @@ -408,16 +380,16 @@ if_std! { } } - impl OptionIntoWasmAbi for Box<[T]> where T: JsObject { + impl OptionVectorIntoWasmAbi for T where T: JsObject { #[inline] - fn none() -> WasmSlice { null_slice() } + fn vector_none() -> WasmSlice { null_slice() } } - impl FromWasmAbi for Box<[T]> where T: JsObject { + impl VectorFromWasmAbi for T where T: JsObject { type Abi = WasmSlice; #[inline] - unsafe fn from_abi(js: WasmSlice) -> Self { + unsafe fn vector_from_abi(js: WasmSlice) -> Box<[T]> { let ptr = <*mut JsValue>::from_abi(js.ptr); let len = js.len as usize; let vec: Vec = Vec::from_raw_parts(ptr, len, len).drain(..).map(|js_value| T::unchecked_from_js(js_value)).collect(); @@ -425,8 +397,8 @@ if_std! { } } - impl OptionFromWasmAbi for Box<[T]> where T: JsObject { + impl OptionVectorFromWasmAbi for T where T: JsObject { #[inline] - fn is_none(slice: &WasmSlice) -> bool { slice.ptr == 0 } + fn vector_is_none(slice: &WasmSlice) -> bool { slice.ptr == 0 } } } diff --git a/src/convert/traits.rs b/src/convert/traits.rs index dda66365012..8c8196290af 100644 --- a/src/convert/traits.rs +++ b/src/convert/traits.rs @@ -127,3 +127,46 @@ impl ReturnWasmAbi for T { self.into_abi() } } + +/// Enables blanket implementations of `WasmDescribe`, `IntoWasmAbi`, +/// `FromWasmAbi` and `OptionIntoWasmAbi` functionality on boxed slices of +/// types which can be converted to and from `JsValue` without conflicting +/// implementations of those traits. +/// +/// Implementing these traits directly with blanket implementations would +/// be much more elegant, but unfortunately that's impossible because it +/// conflicts with the implementations for `Box<[T]> where T: JsObject`. +pub trait JsValueVector { + type ToAbi; + type FromAbi; + + fn describe(); + fn into_abi(self) -> Self::ToAbi; + fn none() -> Self::ToAbi; + unsafe fn from_abi(js: Self::FromAbi) -> Self; +} + +if_std! { + use std::boxed::Box; + use core::marker::Sized; + + pub trait VectorIntoWasmAbi: WasmDescribeVector + Sized { + type Abi: WasmAbi; + + fn vector_into_abi(vector: Box<[Self]>) -> Self::Abi; + } + + pub trait OptionVectorIntoWasmAbi: VectorIntoWasmAbi { + fn vector_none() -> Self::Abi; + } + + pub trait VectorFromWasmAbi: WasmDescribeVector + Sized { + type Abi: WasmAbi; + + unsafe fn vector_from_abi(js: Self::Abi) -> Box<[Self]>; + } + + pub trait OptionVectorFromWasmAbi: VectorFromWasmAbi { + fn vector_is_none(abi: &Self::Abi) -> bool; + } +} diff --git a/src/describe.rs b/src/describe.rs index 88c0eea48a6..5f7d2d896cf 100644 --- a/src/describe.rs +++ b/src/describe.rs @@ -55,6 +55,10 @@ pub trait WasmDescribe { fn describe(); } +pub trait WasmDescribeVector { + fn describe_vector(); +} + macro_rules! simple { ($($t:ident => $d:ident)*) => ($( impl WasmDescribe for $t { @@ -143,15 +147,21 @@ if_std! { } } - impl WasmDescribe for Box<[JsValue]> { - fn describe() { + impl WasmDescribeVector for JsValue { + fn describe_vector() { inform(VECTOR); JsValue::describe(); } } - impl WasmDescribe for Box<[T]> where T: JsObject { + impl WasmDescribe for Box<[T]> { fn describe() { + T::describe_vector(); + } + } + + impl WasmDescribeVector for T { + fn describe_vector() { inform(VECTOR); T::describe(); } From 833c37ffb7d233db5c4bdea06266ce1fc8411363 Mon Sep 17 00:00:00 2001 From: Billy Bradley Date: Tue, 7 Dec 2021 13:59:44 +0000 Subject: [PATCH 3/7] Remove unneeded require --- crates/cli-support/src/js/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 0828cc72d88..6b73f83840f 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -2915,7 +2915,6 @@ impl<'a> Context<'a> { assert!(!variadic); assert_eq!(args.len(), 1); self.require_class_unwrap(class); - self.expose_take_object(); Ok(format!("{}.__unwrap({})", class, args[0])) } } From e0bcda0b3e6f88bf0627494fdefcae9f52814797 Mon Sep 17 00:00:00 2001 From: Billy Bradley Date: Tue, 7 Dec 2021 14:14:12 +0000 Subject: [PATCH 4/7] Move uses out of if_std --- src/convert/slices.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/convert/slices.rs b/src/convert/slices.rs index c4992a9c496..cb396b02fec 100644 --- a/src/convert/slices.rs +++ b/src/convert/slices.rs @@ -1,6 +1,8 @@ #[cfg(feature = "std")] use std::prelude::v1::*; +use core::{slice, str}; + use cfg_if::cfg_if; use crate::cast::JsObject; @@ -10,7 +12,7 @@ use crate::convert::{VectorFromWasmAbi, VectorIntoWasmAbi}; use crate::describe::{self, WasmDescribe, WasmDescribeVector}; if_std! { - use core::{mem, slice, str}; + use core::mem; use std::convert::TryFrom; use crate::convert::{OptionFromWasmAbi, OptionIntoWasmAbi, JsValueVector}; } From e5c9e0e5ae6dc3db467031763ac1fa3dbadb2ea4 Mon Sep 17 00:00:00 2001 From: Billy Bradley Date: Tue, 7 Dec 2021 14:25:03 +0000 Subject: [PATCH 5/7] Add documentation --- src/convert/traits.rs | 8 ++++++++ src/describe.rs | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/convert/traits.rs b/src/convert/traits.rs index 8c8196290af..db70efa6e16 100644 --- a/src/convert/traits.rs +++ b/src/convert/traits.rs @@ -150,22 +150,30 @@ if_std! { use std::boxed::Box; use core::marker::Sized; + /// Trait for element types to implement IntoWasmAbi for vectors of + /// themselves. pub trait VectorIntoWasmAbi: WasmDescribeVector + Sized { type Abi: WasmAbi; fn vector_into_abi(vector: Box<[Self]>) -> Self::Abi; } + /// Trait for element types to implement OptionIntoWasmAbi for vectors + /// of themselves. pub trait OptionVectorIntoWasmAbi: VectorIntoWasmAbi { fn vector_none() -> Self::Abi; } + /// Trait for element types to implement FromWasmAbi for vectors of + /// themselves. pub trait VectorFromWasmAbi: WasmDescribeVector + Sized { type Abi: WasmAbi; unsafe fn vector_from_abi(js: Self::Abi) -> Box<[Self]>; } + /// Trait for element types to implement OptionFromWasmAbi for vectors + /// of themselves. pub trait OptionVectorFromWasmAbi: VectorFromWasmAbi { fn vector_is_none(abi: &Self::Abi) -> bool; } diff --git a/src/describe.rs b/src/describe.rs index 5f7d2d896cf..0db55173d6e 100644 --- a/src/describe.rs +++ b/src/describe.rs @@ -55,6 +55,8 @@ pub trait WasmDescribe { fn describe(); } +/// Trait for element types to implement IntoWasmAbi for vectors of +/// themselves. pub trait WasmDescribeVector { fn describe_vector(); } From 10d6985cf630d090b7a4338a92a8694f8ff6e1a2 Mon Sep 17 00:00:00 2001 From: Billy Bradley Date: Tue, 7 Dec 2021 14:36:21 +0000 Subject: [PATCH 6/7] Move incorrect use statements --- crates/backend/src/codegen.rs | 37 +++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 7c0f4034fcc..f3ff792a302 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -295,37 +295,48 @@ impl ToTokens for ast::Struct { } impl wasm_bindgen::describe::WasmDescribeVector for #name { - use wasm_bindgen::__rt::std::boxed::Box; - fn describe_vector() { + use wasm_bindgen::__rt::std::boxed::Box; as wasm_bindgen::convert::JsValueVector>::describe(); } } impl wasm_bindgen::convert::VectorIntoWasmAbi for #name { - use wasm_bindgen::__rt::std::boxed::Box; - - type Abi = as wasm_bindgen::convert::JsValueVector>::ToAbi; + type Abi = < + wasm_bindgen::__rt::std::boxed::Box<[#name]> + as wasm_bindgen::convert::JsValueVector + >::ToAbi; + + fn vector_into_abi( + vector: wasm_bindgen::__rt::std::boxed::Box<[#name]> + ) -> Self::Abi { + use wasm_bindgen::__rt::std::boxed::Box; - fn vector_into_abi(vector: Box<[#name]>) -> Self::Abi { as wasm_bindgen::convert::JsValueVector>::into_abi(vector) } } impl wasm_bindgen::convert::OptionVectorIntoWasmAbi for #name { - use wasm_bindgen::__rt::std::boxed::Box; - - fn vector_none() -> as wasm_bindgen::convert::JsValueVector>::ToAbi { + fn vector_none() -> < + wasm_bindgen::__rt::std::boxed::Box<[#name]> + as wasm_bindgen::convert::JsValueVector + >::ToAbi { + use wasm_bindgen::__rt::std::boxed::Box; as wasm_bindgen::convert::JsValueVector>::none() } } impl wasm_bindgen::convert::VectorFromWasmAbi for #name { - use wasm_bindgen::__rt::std::boxed::Box; - - type Abi = as wasm_bindgen::convert::JsValueVector>::FromAbi; + type Abi = < + wasm_bindgen::__rt::std::boxed::Box<[#name]> + as wasm_bindgen::convert::JsValueVector + >::FromAbi; + + unsafe fn vector_from_abi( + js: Self::Abi + ) -> wasm_bindgen::__rt::std::boxed::Box<[#name]> { + use wasm_bindgen::__rt::std::boxed::Box; - unsafe fn vector_from_abi(js: Self::Abi) -> Box<[#name]> { as wasm_bindgen::convert::JsValueVector>::from_abi(js) } } From af0c6a4397180a2ec7aff7c412d5a7363bdb9cf7 Mon Sep 17 00:00:00 2001 From: Billy Bradley Date: Tue, 21 Dec 2021 19:19:22 +0000 Subject: [PATCH 7/7] Fix mistake in comment --- src/describe.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/describe.rs b/src/describe.rs index f206a048968..72e69390ab3 100644 --- a/src/describe.rs +++ b/src/describe.rs @@ -56,7 +56,7 @@ pub trait WasmDescribe { fn describe(); } -/// Trait for element types to implement IntoWasmAbi for vectors of +/// Trait for element types to implement WasmDescribe for vectors of /// themselves. pub trait WasmDescribeVector { fn describe_vector();