diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 08e6c31869c..837245d5e6d 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -164,9 +164,10 @@ impl ToTokens for ast::Struct { let name = &self.rust_name; let name_str = self.js_name.to_string(); let name_len = name_str.len() as u32; - let name_chars = name_str.chars().map(|c| c as u32); + let name_chars: Vec = name_str.chars().map(|c| c as u32).collect(); 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 wasm_bindgen = &self.wasm_bindgen; (quote! { #[automatically_derived] @@ -293,6 +294,76 @@ 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<#wasm_bindgen::JsValue> for #name { + type Error = #wasm_bindgen::JsValue; + + fn try_from(value: #wasm_bindgen::JsValue) + -> #wasm_bindgen::__rt::std::result::Result { + let idx = #wasm_bindgen::convert::IntoWasmAbi::into_abi(&value); + + #[link(wasm_import_module = "__wbindgen_placeholder__")] + #[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))] + extern "C" { + fn #unwrap_fn(ptr: u32) -> u32; + } + + #[cfg(not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))))] + unsafe fn #unwrap_fn(_: u32) -> u32 { + panic!("cannot convert from JsValue outside of the wasm target") + } + + let ptr = unsafe { #unwrap_fn(idx) }; + if ptr == 0 { + #wasm_bindgen::__rt::std::result::Result::Err(value) + } else { + // Don't run `JsValue`'s destructor, `unwrap_fn` already did that for us. + #wasm_bindgen::__rt::std::mem::forget(value); + unsafe { + #wasm_bindgen::__rt::std::result::Result::Ok( + ::from_abi(ptr) + ) + } + } + } + } + + impl #wasm_bindgen::describe::WasmDescribeVector for #name { + fn describe_vector() { + use #wasm_bindgen::describe::*; + inform(VECTOR); + inform(NAMED_EXTERNREF); + inform(#name_len); + #(inform(#name_chars);)* + } + } + + impl #wasm_bindgen::convert::VectorIntoWasmAbi for #name { + type Abi = < + #wasm_bindgen::__rt::std::boxed::Box<[#wasm_bindgen::JsValue]> + as #wasm_bindgen::convert::IntoWasmAbi + >::Abi; + + fn vector_into_abi( + vector: #wasm_bindgen::__rt::std::boxed::Box<[#name]> + ) -> Self::Abi { + #wasm_bindgen::convert::js_value_vector_into_abi(vector) + } + } + + impl #wasm_bindgen::convert::VectorFromWasmAbi for #name { + type Abi = < + #wasm_bindgen::__rt::std::boxed::Box<[#wasm_bindgen::JsValue]> + as #wasm_bindgen::convert::FromWasmAbi + >::Abi; + + unsafe fn vector_from_abi( + js: Self::Abi + ) -> #wasm_bindgen::__rt::std::boxed::Box<[#name]> { + #wasm_bindgen::convert::js_value_vector_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 56847262544..86dcc058119 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -75,6 +75,7 @@ pub struct ExportedClass { generate_typescript: bool, 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 @@ -935,6 +936,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 >>> 0));", @@ -2247,6 +2262,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 @@ -3213,6 +3232,14 @@ impl<'a> Context<'a> { See https://rustwasm.github.io/wasm-bindgen/reference/cli.html#--split-linked-modules for details.", path)) } } + + 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 38402aafe9d..bfb6c42b814 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -934,17 +934,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 d05761bfda0..fe8142d027e 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -336,6 +336,11 @@ pub enum AuxImport { /// The Option may contain the contents of the linked file, so it can be /// embedded. LinkTo(String, Option), + + /// 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 85da8965291..4b22e6f97c6 100644 --- a/crates/cli-support/src/wit/section.rs +++ b/crates/cli-support/src/wit/section.rs @@ -273,6 +273,9 @@ fn check_standard_import(import: &AuxImport) -> Result<(), Error> { format!("wasm-bindgen specific link function for `{}`", path) } 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/macro/ui-tests/missing-catch.stderr b/crates/macro/ui-tests/missing-catch.stderr index 55466de06d6..5e54e41e7f7 100644 --- a/crates/macro/ui-tests/missing-catch.stderr +++ b/crates/macro/ui-tests/missing-catch.stderr @@ -8,9 +8,9 @@ error[E0277]: the trait bound `Result - Box<[f32]> - Box<[f64]> - Box<[i16]> - Box<[i32]> - Box<[i64]> - and 35 others + Clamped + Option + Option + Option + Option + and $N others diff --git a/crates/macro/ui-tests/traits-not-implemented.stderr b/crates/macro/ui-tests/traits-not-implemented.stderr index 5b8e122287f..714050a53b5 100644 --- a/crates/macro/ui-tests/traits-not-implemented.stderr +++ b/crates/macro/ui-tests/traits-not-implemented.stderr @@ -13,5 +13,5 @@ error[E0277]: the trait bound `A: IntoWasmAbi` is not satisfied &'a (dyn Fn(A, B, C, D, E) -> R + 'b) &'a (dyn Fn(A, B, C, D, E, F) -> R + 'b) &'a (dyn Fn(A, B, C, D, E, F, G) -> R + 'b) - and 84 others + and $N others = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index fb3e92cd95f..2b1ba52b3c9 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -165,6 +165,13 @@ pub fn free_function(struct_name: &str) -> String { name } +pub fn unwrap_function(struct_name: &str) -> String { + let mut name = "__wbg_".to_string(); + name.extend(struct_name.chars().flat_map(|s| s.to_lowercase())); + name.push_str("_unwrap"); + name +} + pub fn free_function_export_name(function_name: &str) -> String { function_name.to_string() } diff --git a/crates/shared/src/schema_hash_approval.rs b/crates/shared/src/schema_hash_approval.rs index 731dcea0214..4217bde716e 100644 --- a/crates/shared/src/schema_hash_approval.rs +++ b/crates/shared/src/schema_hash_approval.rs @@ -8,7 +8,7 @@ // If the schema in this library has changed then: // 1. Bump the version in `crates/shared/Cargo.toml` // 2. Change the `SCHEMA_VERSION` in this library to this new Cargo.toml version -const APPROVED_SCHEMA_FILE_HASH: &str = "12040133795598472740"; +const APPROVED_SCHEMA_FILE_HASH: &str = "5679641936258023729"; #[test] fn schema_version() { diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 9ef9c260ff6..5d9c362902c 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -52,7 +52,7 @@ - [Imported JavaScript Types](./reference/types/imported-js-types.md) - [Exported Rust Types](./reference/types/exported-rust-types.md) - [`JsValue`](./reference/types/jsvalue.md) - - [`Box<[JsValue]>`](./reference/types/boxed-jsvalue-slice.md) + - [`Box<[T]>` and `Vec`](./reference/types/boxed-slices.md) - [`*const T` and `*mut T`](./reference/types/pointers.md) - [Numbers](./reference/types/numbers.md) - [`bool`](./reference/types/bool.md) diff --git a/guide/src/reference/types/boxed-jsvalue-slice.md b/guide/src/reference/types/boxed-slices.md similarity index 58% rename from guide/src/reference/types/boxed-jsvalue-slice.md rename to guide/src/reference/types/boxed-slices.md index 8060d194a7c..ed299462292 100644 --- a/guide/src/reference/types/boxed-jsvalue-slice.md +++ b/guide/src/reference/types/boxed-slices.md @@ -1,10 +1,19 @@ -# `Box<[JsValue]>` +# `Box<[T]>` and `Vec` | `T` parameter | `&T` parameter | `&mut T` parameter | `T` return value | `Option` parameter | `Option` return value | JavaScript representation | |:---:|:---:|:---:|:---:|:---:|:---:|:---:| | Yes | No | No | Yes | Yes | Yes | A JavaScript `Array` object | -Boxed slices of imported JS types and exported Rust types are also supported. `Vec` is supported wherever `Box<[T]>` is. +You can pass boxed slices and `Vec`s of several different types to and from JS: + +- `JsValue`s. +- Imported JavaScript types. +- Exported Rust types. +- `String`s. + +[You can also pass boxed slices of numbers to JS](boxed-number-slices.html), +except that they're converted to typed arrays (`Uint8Array`, `Int32Array`, etc.) +instead of regular arrays. ## Example Rust Usage diff --git a/src/convert/impls.rs b/src/convert/impls.rs index e8636fe9e66..637d9d979ad 100644 --- a/src/convert/impls.rs +++ b/src/convert/impls.rs @@ -4,7 +4,14 @@ use core::mem::{self, ManuallyDrop}; use crate::convert::traits::WasmAbi; use crate::convert::{FromWasmAbi, IntoWasmAbi, LongRefFromWasmAbi, RefFromWasmAbi}; use crate::convert::{OptionFromWasmAbi, OptionIntoWasmAbi, ReturnWasmAbi}; -use crate::{Clamped, JsError, JsValue}; +use crate::{Clamped, JsError, JsValue, UnwrapThrowExt}; + +if_std! { + use std::boxed::Box; + use std::convert::{TryFrom, TryInto}; + use std::fmt::Debug; + use std::vec::Vec; +} unsafe impl WasmAbi for () {} @@ -321,7 +328,7 @@ impl IntoWasmAbi for () { /// - u32/i32/f32/f64 fields at the "leaf fields" of the "field tree" /// - layout equivalent to a completely flattened repr(C) struct, constructed by an in order /// traversal of all the leaf fields in it. -/// +/// /// This means that you can't embed struct A(u32, f64) as struct B(u32, A); because the "completely /// flattened" struct AB(u32, u32, f64) would miss the 4 byte padding that is actually present /// within B and then as a consequence also miss the 4 byte padding within A that repr(C) inserts. @@ -386,3 +393,37 @@ impl IntoWasmAbi for JsError { self.value.into_abi() } } + +if_std! { + // Note: this can't take `&[T]` because the `Into` impl needs + // ownership of `T`. + pub fn js_value_vector_into_abi>(vector: Box<[T]>) -> as IntoWasmAbi>::Abi { + let js_vals: Box<[JsValue]> = vector + .into_vec() + .into_iter() + .map(|x| x.into()) + .collect(); + + js_vals.into_abi() + } + + pub unsafe fn js_value_vector_from_abi>(js: as FromWasmAbi>::Abi) -> Box<[T]> where T::Error: Debug { + let js_vals = as FromWasmAbi>::from_abi(js); + + let mut result = Vec::with_capacity(js_vals.len()); + for value in js_vals { + // We push elements one-by-one instead of using `collect` in order to improve + // error messages. When using `collect`, this `expect_throw` is buried in a + // giant chain of internal iterator functions, which results in the actual + // function that takes this `Vec` falling off the end of the call stack. + // So instead, make sure to call it directly within this function. + // + // This is only a problem in debug mode. Since this is the browser's error stack + // we're talking about, it can only see functions that actually make it to the + // final wasm binary (i.e., not inlined functions). All of those internal + // iterator functions get inlined in release mode, and so they don't show up. + result.push(value.try_into().expect_throw("array contains a value of the wrong type")); + } + result.into_boxed_slice() + } +} diff --git a/src/convert/slices.rs b/src/convert/slices.rs index 40ea6edc7c8..b356df5eb27 100644 --- a/src/convert/slices.rs +++ b/src/convert/slices.rs @@ -10,11 +10,14 @@ use crate::convert::OptionIntoWasmAbi; use crate::convert::{ FromWasmAbi, IntoWasmAbi, LongRefFromWasmAbi, RefFromWasmAbi, RefMutFromWasmAbi, WasmAbi, }; +use crate::convert::{VectorFromWasmAbi, VectorIntoWasmAbi}; +use crate::describe::*; use cfg_if::cfg_if; if_std! { use core::mem; use crate::convert::OptionFromWasmAbi; + use crate::convert::{js_value_vector_from_abi, js_value_vector_into_abi}; } #[repr(C)] @@ -77,14 +80,21 @@ if_std! { macro_rules! vectors { ($($t:ident)*) => ($( if_std! { - impl IntoWasmAbi for Box<[$t]> { + impl WasmDescribeVector for $t { + fn describe_vector() { + inform(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, @@ -92,26 +102,16 @@ macro_rules! vectors { } } - impl OptionIntoWasmAbi for Box<[$t]> { - #[inline] - fn 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]> { - #[inline] - fn is_none(slice: &WasmSlice) -> bool { slice.ptr == 0 } - } } impl<'a> IntoWasmAbi for &'a [$t] { @@ -183,6 +183,39 @@ vectors! { u8 i8 u16 i16 u32 i32 u64 i64 usize isize f32 f64 } +if_std! { + impl WasmDescribeVector for String { + fn describe_vector() { + inform(VECTOR); + inform(NAMED_EXTERNREF); + // Trying to use an actual loop for this breaks the wasm interpreter. + inform(6); + inform('s' as u32); + inform('t' as u32); + inform('r' as u32); + inform('i' as u32); + inform('n' as u32); + inform('g' as u32); + } + } + + impl VectorIntoWasmAbi for String { + type Abi = as IntoWasmAbi>::Abi; + + fn vector_into_abi(vector: Box<[Self]>) -> Self::Abi { + js_value_vector_into_abi(vector) + } + } + + impl VectorFromWasmAbi for String { + type Abi = as FromWasmAbi>::Abi; + + unsafe fn vector_from_abi(js: Self::Abi) -> Box<[Self]> { + js_value_vector_from_abi(js) + } + } +} + cfg_if! { if #[cfg(feature = "enable-interning")] { #[inline] @@ -300,14 +333,42 @@ impl LongRefFromWasmAbi 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]> where Self: IntoWasmAbi { + fn none() -> WasmSlice { + null_slice() + } + } + + 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]> where Self: FromWasmAbi { + fn is_none(slice: &WasmSlice) -> bool { + slice.ptr == 0 + } + } + + 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, @@ -315,35 +376,25 @@ if_std! { } } - impl OptionIntoWasmAbi for Box<[JsValue]> { - #[inline] - fn 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]> { - #[inline] - fn 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, @@ -351,25 +402,15 @@ if_std! { } } - impl OptionIntoWasmAbi for Box<[T]> where T: JsObject { - #[inline] - fn 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(); vec.into_boxed_slice() } } - - impl OptionFromWasmAbi for Box<[T]> where T: JsObject { - #[inline] - fn is_none(slice: &WasmSlice) -> bool { slice.ptr == 0 } - } } diff --git a/src/convert/traits.rs b/src/convert/traits.rs index fe31aab63a4..dc74caae15a 100644 --- a/src/convert/traits.rs +++ b/src/convert/traits.rs @@ -158,3 +158,24 @@ impl ReturnWasmAbi for T { self.into_abi() } } + +if_std! { + use core::marker::Sized; + use std::boxed::Box; + + /// 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 FromWasmAbi for vectors of + /// themselves. + pub trait VectorFromWasmAbi: WasmDescribeVector + Sized { + type Abi: WasmAbi; + + unsafe fn vector_from_abi(js: Self::Abi) -> Box<[Self]>; + } +} diff --git a/src/describe.rs b/src/describe.rs index be149d7a90b..12a48554ac6 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 { @@ -57,6 +57,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 { @@ -145,13 +151,26 @@ if_std! { } } - impl WasmDescribe for Box<[T]> { - fn describe() { + impl WasmDescribeVector for JsValue { + fn describe_vector() { + inform(VECTOR); + JsValue::describe(); + } + } + + impl WasmDescribeVector for T { + fn describe_vector() { inform(VECTOR); T::describe(); } } + impl WasmDescribe for Box<[T]> { + fn describe() { + T::describe_vector(); + } + } + impl WasmDescribe for Vec where Box<[T]>: WasmDescribe { fn describe() { >::describe(); diff --git a/src/lib.rs b/src/lib.rs index 1df9cd92b0d..44844f63b8b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -805,6 +805,17 @@ if_std! { JsValue::from_str(&s) } } + + impl TryFrom for String { + type Error = JsValue; + + fn try_from(value: JsValue) -> Result { + match value.as_string() { + Some(s) => Ok(s), + None => Err(value), + } + } + } } impl From for JsValue { diff --git a/tests/wasm/main.rs b/tests/wasm/main.rs index 508a9ea1f78..eebde19677e 100644 --- a/tests/wasm/main.rs +++ b/tests/wasm/main.rs @@ -46,6 +46,8 @@ pub mod result_jserror; pub mod rethrow; pub mod simple; pub mod slice; +pub mod string_vecs; +pub mod struct_vecs; pub mod structural; pub mod truthy_falsy; pub mod usize; diff --git a/tests/wasm/slice.js b/tests/wasm/slice.js index c23535e9b1f..3e65dfc5238 100644 --- a/tests/wasm/slice.js +++ b/tests/wasm/slice.js @@ -6,39 +6,60 @@ exports.js_export = () => { i8[0] = 1; i8[1] = 2; assert.deepStrictEqual(wasm.export_i8(i8), i8); + assert.deepStrictEqual(wasm.export_optional_i8(i8), i8); const u8 = new Uint8Array(2); u8[0] = 1; u8[1] = 2; assert.deepStrictEqual(wasm.export_u8(u8), u8); + assert.deepStrictEqual(wasm.export_optional_u8(u8), u8); const i16 = new Int16Array(2); i16[0] = 1; i16[1] = 2; assert.deepStrictEqual(wasm.export_i16(i16), i16); + assert.deepStrictEqual(wasm.export_optional_i16(i16), i16); const u16 = new Uint16Array(2); u16[0] = 1; u16[1] = 2; assert.deepStrictEqual(wasm.export_u16(u16), u16); + assert.deepStrictEqual(wasm.export_optional_u16(u16), u16); const i32 = new Int32Array(2); i32[0] = 1; i32[1] = 2; assert.deepStrictEqual(wasm.export_i32(i32), i32); + assert.deepStrictEqual(wasm.export_optional_i32(i32), i32); assert.deepStrictEqual(wasm.export_isize(i32), i32); + assert.deepStrictEqual(wasm.export_optional_isize(i32), i32); const u32 = new Uint32Array(2); u32[0] = 1; u32[1] = 2; assert.deepStrictEqual(wasm.export_u32(u32), u32); + assert.deepStrictEqual(wasm.export_optional_u32(u32), u32); assert.deepStrictEqual(wasm.export_usize(u32), u32); + assert.deepStrictEqual(wasm.export_optional_usize(u32), u32); const f32 = new Float32Array(2); f32[0] = 1; f32[1] = 2; assert.deepStrictEqual(wasm.export_f32(f32), f32); + assert.deepStrictEqual(wasm.export_optional_f32(f32), f32); const f64 = new Float64Array(2); f64[0] = 1; f64[1] = 2; assert.deepStrictEqual(wasm.export_f64(f64), f64); + assert.deepStrictEqual(wasm.export_optional_f64(f64), f64); + + assert.strictEqual(wasm.export_optional_i8(undefined), undefined); + assert.strictEqual(wasm.export_optional_u8(undefined), undefined); + assert.strictEqual(wasm.export_optional_i16(undefined), undefined); + assert.strictEqual(wasm.export_optional_u16(undefined), undefined); + assert.strictEqual(wasm.export_optional_i32(undefined), undefined); + assert.strictEqual(wasm.export_optional_isize(undefined), undefined); + assert.strictEqual(wasm.export_optional_u32(undefined), undefined); + assert.strictEqual(wasm.export_optional_usize(undefined), undefined); + assert.strictEqual(wasm.export_optional_f32(undefined), undefined); + assert.strictEqual(wasm.export_optional_f64(undefined), undefined); }; const test_import = (a, b, c) => { diff --git a/tests/wasm/slice.rs b/tests/wasm/slice.rs index 6e659ce6f54..9f9066fe720 100644 --- a/tests/wasm/slice.rs +++ b/tests/wasm/slice.rs @@ -22,7 +22,7 @@ extern "C" { } macro_rules! export_macro { - ($(($i:ident, $n:ident))*) => ($( + ($(($i:ident, $n:ident, $optional_n:ident))*) => ($( #[wasm_bindgen] pub fn $n(a: &[$i]) -> Vec<$i> { assert_eq!(a.len(), 2); @@ -30,20 +30,30 @@ macro_rules! export_macro { assert_eq!(a[1], 2 as $i); a.to_vec() } + + #[wasm_bindgen] + pub fn $optional_n(a: Option>) -> Option> { + a.map(|a| { + assert_eq!(a.len(), 2); + assert_eq!(a[0], 1 as $i); + assert_eq!(a[1], 2 as $i); + a.to_vec() + }) + } )*) } export_macro! { - (i8, export_i8) - (u8, export_u8) - (i16, export_i16) - (u16, export_u16) - (i32, export_i32) - (u32, export_u32) - (isize, export_isize) - (usize, export_usize) - (f32, export_f32) - (f64, export_f64) + (i8, export_i8, export_optional_i8) + (u8, export_u8, export_optional_u8) + (i16, export_i16, export_optional_i16) + (u16, export_u16, export_optional_u16) + (i32, export_i32, export_optional_i32) + (u32, export_u32, export_optional_u32) + (isize, export_isize, export_optional_isize) + (usize, export_usize, export_optional_usize) + (f32, export_f32, export_optional_f32) + (f64, export_f64, export_optional_f64) } #[wasm_bindgen_test] diff --git a/tests/wasm/string_vecs.js b/tests/wasm/string_vecs.js new file mode 100644 index 00000000000..de9b0ef580a --- /dev/null +++ b/tests/wasm/string_vecs.js @@ -0,0 +1,23 @@ +const wasm = require('wasm-bindgen-test.js'); +const assert = require('assert'); + +exports.pass_string_vec = () => { + assert.deepStrictEqual( + wasm.consume_string_vec(["hello", "world"]), + ["hello", "world", "Hello from Rust!"], + ); + assert.deepStrictEqual( + wasm.consume_optional_string_vec(["hello", "world"]), + ["hello", "world", "Hello from Rust!"], + ); + assert.strictEqual(wasm.consume_optional_string_vec(undefined), undefined); +}; + +exports.pass_invalid_string_vec = () => { + try { + wasm.consume_string_vec([42]); + } catch (e) { + assert.match(e.message, /array contains a value of the wrong type/) + assert.match(e.stack, /consume_string_vec/) + } +}; diff --git a/tests/wasm/string_vecs.rs b/tests/wasm/string_vecs.rs new file mode 100644 index 00000000000..1234d03d9b0 --- /dev/null +++ b/tests/wasm/string_vecs.rs @@ -0,0 +1,29 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen_test::*; + +#[wasm_bindgen(module = "tests/wasm/string_vecs.js")] +extern "C" { + fn pass_string_vec(); + fn pass_invalid_string_vec(); +} + +#[wasm_bindgen] +pub fn consume_string_vec(mut vec: Vec) -> Vec { + vec.push("Hello from Rust!".to_owned()); + vec +} + +#[wasm_bindgen] +pub fn consume_optional_string_vec(vec: Option>) -> Option> { + vec.map(consume_string_vec) +} + +#[wasm_bindgen_test] +fn test_valid() { + pass_string_vec(); +} + +#[wasm_bindgen_test] +fn test_invalid() { + pass_invalid_string_vec(); +} diff --git a/tests/wasm/struct_vecs.js b/tests/wasm/struct_vecs.js new file mode 100644 index 00000000000..23eb154fd80 --- /dev/null +++ b/tests/wasm/struct_vecs.js @@ -0,0 +1,23 @@ +const wasm = require('wasm-bindgen-test.js'); +const assert = require('assert'); + +exports.pass_struct_vec = () => { + const el1 = new wasm.ArrayElement(); + const el2 = new wasm.ArrayElement(); + const ret = wasm.consume_struct_vec([el1, el2]); + assert.strictEqual(ret.length, 3); + + const ret2 = wasm.consume_optional_struct_vec(ret); + assert.strictEqual(ret2.length, 4); + + assert.strictEqual(wasm.consume_optional_struct_vec(undefined), undefined); +}; + +exports.pass_invalid_struct_vec = () => { + try { + wasm.consume_struct_vec(['not a struct']); + } catch (e) { + assert.match(e.message, /array contains a value of the wrong type/) + assert.match(e.stack, /consume_struct_vec/) + } +}; diff --git a/tests/wasm/struct_vecs.rs b/tests/wasm/struct_vecs.rs new file mode 100644 index 00000000000..2440abc2037 --- /dev/null +++ b/tests/wasm/struct_vecs.rs @@ -0,0 +1,40 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen_test::*; + +#[wasm_bindgen(module = "tests/wasm/struct_vecs.js")] +extern "C" { + fn pass_struct_vec(); + fn pass_invalid_struct_vec(); +} + +#[wasm_bindgen] +pub struct ArrayElement; + +#[wasm_bindgen] +impl ArrayElement { + #[wasm_bindgen(constructor)] + pub fn new() -> ArrayElement { + ArrayElement + } +} + +#[wasm_bindgen] +pub fn consume_struct_vec(mut vec: Vec) -> Vec { + vec.push(ArrayElement); + vec +} + +#[wasm_bindgen] +pub fn consume_optional_struct_vec(vec: Option>) -> Option> { + vec.map(consume_struct_vec) +} + +#[wasm_bindgen_test] +fn test_valid() { + pass_struct_vec(); +} + +#[wasm_bindgen_test] +fn test_invalid() { + pass_invalid_struct_vec(); +}