diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 601d0abc786..b14726658d7 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -148,6 +148,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()); let free_fn_const = Ident::new(&format!("{}__const", free_fn), free_fn.span()); (quote! { #[automatically_derived] @@ -266,6 +267,90 @@ impl ToTokens for ast::Struct { #[inline] 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 { + fn describe_vector() { + use wasm_bindgen::__rt::std::boxed::Box; + as wasm_bindgen::convert::JsValueVector>::describe(); + } + } + + impl wasm_bindgen::convert::VectorIntoWasmAbi for #name { + 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; + + as wasm_bindgen::convert::JsValueVector>::into_abi(vector) + } + } + + impl wasm_bindgen::convert::OptionVectorIntoWasmAbi for #name { + 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 { + 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; + + 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 b0b5136a9d9..6e9cdd110e8 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 @@ -2918,6 +2937,14 @@ 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); + 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 3154a48b86a..4cb0bd95579 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 a1ac9eb5bde..2b88a579a08 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -310,6 +310,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 8ee3f84019b..903be14752f 100644 --- a/crates/cli-support/src/wit/section.rs +++ b/crates/cli-support/src/wit/section.rs @@ -357,6 +357,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 429a32896af..66b7e20fcf9 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, JsError, 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)] @@ -465,6 +473,43 @@ 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() + } + } +} + impl IntoWasmAbi for JsError { type Abi = ::Abi; diff --git a/src/convert/slices.rs b/src/convert/slices.rs index 9d0970f4e6a..cb396b02fec 100644 --- a/src/convert/slices.rs +++ b/src/convert/slices.rs @@ -1,17 +1,20 @@ #[cfg(feature = "std")] use std::prelude::v1::*; -use core::slice; -use core::str; +use core::{slice, str}; + +use cfg_if::cfg_if; use crate::cast::JsObject; -use crate::convert::OptionIntoWasmAbi; use crate::convert::{FromWasmAbi, IntoWasmAbi, RefFromWasmAbi, RefMutFromWasmAbi, WasmAbi}; -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 std::convert::TryFrom; + use crate::convert::{OptionFromWasmAbi, OptionIntoWasmAbi, JsValueVector}; } #[repr(C)] @@ -30,14 +33,21 @@ fn null_slice() -> WasmSlice { macro_rules! vectors { ($($t:ident)*) => ($( if_std! { - impl IntoWasmAbi for Box<[$t]> { + impl WasmDescribeVector for $t { + fn describe_vector() { + describe::inform(describe::VECTOR); + $t::describe(); + } + } + + 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, @@ -45,25 +55,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 } } } @@ -104,7 +114,7 @@ macro_rules! vectors { #[inline] unsafe fn ref_from_abi(js: WasmSlice) -> Box<[$t]> { - >::from_abi(js) + as FromWasmAbi>::from_abi(js) } } @@ -129,6 +139,63 @@ vectors! { u8 i8 u16 i16 u32 i32 u64 i64 usize isize f32 f64 } +/* + * 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 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; + + fn into_abi(self) -> Self::Abi { + ::into_abi(self) + } + } + + impl OptionIntoWasmAbi for Box<[$t]> { + fn none() -> ::ToAbi { + ::none() + } + } + + impl FromWasmAbi for Box<[$t]> { + type Abi = ::FromAbi; + + unsafe fn from_abi(js: Self::Abi) -> Self { + ::from_abi(js) + } + } + } + )*) +} + +if_std! { + 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] @@ -236,14 +303,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, @@ -251,35 +346,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, @@ -287,16 +382,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(); @@ -304,8 +399,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..db70efa6e16 100644 --- a/src/convert/traits.rs +++ b/src/convert/traits.rs @@ -127,3 +127,54 @@ 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; + + /// 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 84fab48f779..72e69390ab3 100644 --- a/src/describe.rs +++ b/src/describe.rs @@ -3,7 +3,7 @@ #![doc(hidden)] -use crate::{Clamped, JsError, JsValue}; +use crate::{Clamped, JsError, JsObject, JsValue}; use cfg_if::cfg_if; macro_rules! tys { @@ -56,6 +56,12 @@ pub trait WasmDescribe { fn describe(); } +/// Trait for element types to implement WasmDescribe for vectors of +/// themselves. +pub trait WasmDescribeVector { + fn describe_vector(); +} + macro_rules! simple { ($($t:ident => $d:ident)*) => ($( impl WasmDescribe for $t { @@ -144,8 +150,21 @@ if_std! { } } - impl WasmDescribe for Box<[T]> { + impl WasmDescribeVector for JsValue { + fn describe_vector() { + inform(VECTOR); + JsValue::describe(); + } + } + + impl WasmDescribe for Box<[T]> { fn describe() { + T::describe_vector(); + } + } + + impl WasmDescribeVector for T { + fn describe_vector() { inform(VECTOR); T::describe(); }