diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index b20c7035c9b..57228711749 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -624,7 +624,7 @@ impl ToTokens for ast::ImportType { use wasm_bindgen::convert::{OptionIntoWasmAbi, OptionFromWasmAbi}; use wasm_bindgen::convert::RefFromWasmAbi; use wasm_bindgen::describe::WasmDescribe; - use wasm_bindgen::{JsValue, JsCast}; + use wasm_bindgen::{JsValue, JsCast, JsObject}; use wasm_bindgen::__rt::core; impl WasmDescribe for #rust_name { @@ -761,6 +761,8 @@ impl ToTokens for ast::ImportType { } } + impl JsObject for #rust_name {} + () }; }) diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index d5c4dd7f07a..5950242376a 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -86,7 +86,7 @@ pub struct Closure { pub mutable: bool, } -#[derive(Copy, Clone, Debug)] +#[derive(Clone, Debug)] pub enum VectorKind { I8, U8, @@ -101,6 +101,7 @@ pub enum VectorKind { F64, String, Externref, + NamedExternref(String), } impl Descriptor { @@ -193,6 +194,7 @@ impl Descriptor { Descriptor::F32 => Some(VectorKind::F32), Descriptor::F64 => Some(VectorKind::F64), Descriptor::Externref => Some(VectorKind::Externref), + Descriptor::NamedExternref(ref name) => Some(VectorKind::NamedExternref(name.clone())), _ => None, } } @@ -240,21 +242,24 @@ impl Function { } impl VectorKind { - pub fn js_ty(&self) -> &str { + pub fn js_ty(&self) -> String { match *self { - VectorKind::String => "string", - VectorKind::I8 => "Int8Array", - VectorKind::U8 => "Uint8Array", - VectorKind::ClampedU8 => "Uint8ClampedArray", - VectorKind::I16 => "Int16Array", - VectorKind::U16 => "Uint16Array", - VectorKind::I32 => "Int32Array", - VectorKind::U32 => "Uint32Array", - VectorKind::I64 => "BigInt64Array", - VectorKind::U64 => "BigUint64Array", - VectorKind::F32 => "Float32Array", - VectorKind::F64 => "Float64Array", - VectorKind::Externref => "any[]", + VectorKind::String => "string".to_string(), + VectorKind::I8 => "Int8Array".to_string(), + VectorKind::U8 => "Uint8Array".to_string(), + VectorKind::ClampedU8 => "Uint8ClampedArray".to_string(), + VectorKind::I16 => "Int16Array".to_string(), + VectorKind::U16 => "Uint16Array".to_string(), + VectorKind::I32 => "Int32Array".to_string(), + VectorKind::U32 => "Uint32Array".to_string(), + VectorKind::I64 => "BigInt64Array".to_string(), + VectorKind::U64 => "BigUint64Array".to_string(), + VectorKind::F32 => "Float32Array".to_string(), + VectorKind::F64 => "Float64Array".to_string(), + VectorKind::Externref => "any[]".to_string(), + VectorKind::NamedExternref(ref name) => { + format!("({})[]", name) + } } } @@ -273,6 +278,7 @@ impl VectorKind { VectorKind::F32 => 4, VectorKind::F64 => 8, VectorKind::Externref => 4, + VectorKind::NamedExternref(_) => 4, } } } diff --git a/crates/cli-support/src/externref.rs b/crates/cli-support/src/externref.rs index 5c8a90e8996..11127be4a03 100644 --- a/crates/cli-support/src/externref.rs +++ b/crates/cli-support/src/externref.rs @@ -351,31 +351,31 @@ fn module_needs_externref_metadata(aux: &WasmBindgenAux, section: &NonstandardWi }; instructions.iter().any(|instr| match instr.instr { VectorToMemory { - kind: VectorKind::Externref, + kind: VectorKind::Externref | VectorKind::NamedExternref(_), .. } | MutableSliceToMemory { - kind: VectorKind::Externref, + kind: VectorKind::Externref | VectorKind::NamedExternref(_), .. } | OptionVector { - kind: VectorKind::Externref, + kind: VectorKind::Externref | VectorKind::NamedExternref(_), .. } | VectorLoad { - kind: VectorKind::Externref, + kind: VectorKind::Externref | VectorKind::NamedExternref(_), .. } | OptionVectorLoad { - kind: VectorKind::Externref, + kind: VectorKind::Externref | VectorKind::NamedExternref(_), .. } | View { - kind: VectorKind::Externref, + kind: VectorKind::Externref | VectorKind::NamedExternref(_), .. } | OptionView { - kind: VectorKind::Externref, + kind: VectorKind::Externref | VectorKind::NamedExternref(_), .. } => true, _ => false, diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 643937635b9..ea79adf9279 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -762,7 +762,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> Instruction::VectorToMemory { kind, malloc, mem } => { let val = js.pop(); - let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + let func = js.cx.pass_to_wasm_function(kind.clone(), *mem)?; let malloc = js.cx.export_name_of(*malloc); let i = js.tmp(); js.prelude(&format!( @@ -805,7 +805,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> } Instruction::OptionVector { kind, mem, malloc } => { - let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + let func = js.cx.pass_to_wasm_function(kind.clone(), *mem)?; js.cx.expose_is_like_none(); let i = js.tmp(); let malloc = js.cx.export_name_of(*malloc); @@ -832,7 +832,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> // a length. These two pointer/length values get pushed onto the // value stack. let val = js.pop(); - let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + let func = js.cx.pass_to_wasm_function(kind.clone(), *mem)?; let malloc = js.cx.export_name_of(*malloc); let i = js.tmp(); js.prelude(&format!( @@ -850,7 +850,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> // original mutable slice with any modifications, and then free the // Rust-backed memory. let free = js.cx.export_name_of(*free); - let get = js.cx.memview_function(*kind, *mem); + let get = js.cx.memview_function(kind.clone(), *mem); js.finally(&format!( " {val}.set({get}().subarray(ptr{i} / {size}, ptr{i} / {size} + len{i})); @@ -1003,7 +1003,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> Instruction::VectorLoad { kind, mem, free } => { let len = js.pop(); let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + let f = js.cx.expose_get_vector_from_wasm(kind.clone(), *mem)?; let i = js.tmp(); let free = js.cx.export_name_of(*free); js.prelude(&format!("var v{} = {}({}, {}).slice();", i, f, ptr, len)); @@ -1020,7 +1020,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> Instruction::OptionVectorLoad { kind, mem, free } => { let len = js.pop(); let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + let f = js.cx.expose_get_vector_from_wasm(kind.clone(), *mem)?; let i = js.tmp(); let free = js.cx.export_name_of(*free); js.prelude(&format!("let v{};", i)); @@ -1040,14 +1040,14 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> Instruction::View { kind, mem } => { let len = js.pop(); let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + let f = js.cx.expose_get_vector_from_wasm(kind.clone(), *mem)?; js.push(format!("{f}({ptr}, {len})", ptr = ptr, len = len, f = f)); } Instruction::OptionView { kind, mem } => { let len = js.pop(); let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + let f = js.cx.expose_get_vector_from_wasm(kind.clone(), *mem)?; js.push(format!( "{ptr} === 0 ? undefined : {f}({ptr}, {len})", ptr = ptr, @@ -1226,7 +1226,7 @@ fn adapter2ts(ty: &AdapterType, dst: &mut String) { AdapterType::String => dst.push_str("string"), AdapterType::Externref => dst.push_str("any"), AdapterType::Bool => dst.push_str("boolean"), - AdapterType::Vector(kind) => dst.push_str(kind.js_ty()), + AdapterType::Vector(kind) => dst.push_str(&kind.js_ty()), AdapterType::Option(ty) => { adapter2ts(ty, dst); dst.push_str(" | undefined"); diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 5b9a1b04e17..a286adc8808 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1621,6 +1621,7 @@ impl<'a> Context<'a> { VectorKind::F32 => self.expose_f32_memory(memory), VectorKind::F64 => self.expose_f64_memory(memory), VectorKind::Externref => self.expose_uint32_memory(memory), + VectorKind::NamedExternref(_) => self.expose_uint32_memory(memory), } } @@ -1842,6 +1843,7 @@ impl<'a> Context<'a> { VectorKind::F32 => self.expose_pass_array_f32_to_wasm(memory), VectorKind::F64 => self.expose_pass_array_f64_to_wasm(memory), VectorKind::Externref => self.expose_pass_array_jsvalue_to_wasm(memory), + VectorKind::NamedExternref(_) => self.expose_pass_array_jsvalue_to_wasm(memory), } } @@ -1864,6 +1866,7 @@ impl<'a> Context<'a> { VectorKind::F32 => self.expose_get_array_f32_from_wasm(memory), VectorKind::F64 => self.expose_get_array_f64_from_wasm(memory), VectorKind::Externref => self.expose_get_array_js_value_from_wasm(memory)?, + VectorKind::NamedExternref(_) => self.expose_get_array_js_value_from_wasm(memory)?, }) } diff --git a/crates/cli-support/src/wit/incoming.rs b/crates/cli-support/src/wit/incoming.rs index 05a7d757ba4..a911535f485 100644 --- a/crates/cli-support/src/wit/incoming.rs +++ b/crates/cli-support/src/wit/incoming.rs @@ -121,7 +121,7 @@ impl InstructionBuilder<'_, '_> { format_err!("unsupported argument type for calling Rust function from JS {:?}", arg) })?; self.instruction( - &[AdapterType::Vector(kind)], + &[AdapterType::Vector(kind.clone())], Instruction::VectorToMemory { kind, malloc: self.cx.malloc()?, @@ -198,7 +198,7 @@ impl InstructionBuilder<'_, '_> { })?; if mutable { self.instruction( - &[AdapterType::Vector(kind)], + &[AdapterType::Vector(kind.clone())], Instruction::MutableSliceToMemory { kind, malloc: self.cx.malloc()?, @@ -209,7 +209,7 @@ impl InstructionBuilder<'_, '_> { ); } else { self.instruction( - &[AdapterType::Vector(kind)], + &[AdapterType::Vector(kind.clone())], Instruction::VectorToMemory { kind, malloc: self.cx.malloc()?, @@ -322,7 +322,7 @@ impl InstructionBuilder<'_, '_> { let malloc = self.cx.malloc()?; let mem = self.cx.memory()?; self.instruction( - &[AdapterType::Vector(kind).option()], + &[AdapterType::Vector(kind.clone()).option()], Instruction::OptionVector { kind, malloc, mem }, &[AdapterType::I32, AdapterType::I32], ); diff --git a/crates/cli-support/src/wit/outgoing.rs b/crates/cli-support/src/wit/outgoing.rs index d6b4692c4a0..bba1fe81d8c 100644 --- a/crates/cli-support/src/wit/outgoing.rs +++ b/crates/cli-support/src/wit/outgoing.rs @@ -139,7 +139,11 @@ impl InstructionBuilder<'_, '_> { let free = self.cx.free()?; self.instruction( &[AdapterType::I32, AdapterType::I32], - Instruction::VectorLoad { kind, mem, free }, + Instruction::VectorLoad { + kind: kind.clone(), + mem, + free, + }, &[AdapterType::Vector(kind)], ); } @@ -196,7 +200,10 @@ impl InstructionBuilder<'_, '_> { let mem = self.cx.memory()?; self.instruction( &[AdapterType::I32, AdapterType::I32], - Instruction::View { kind, mem }, + Instruction::View { + kind: kind.clone(), + mem, + }, &[AdapterType::Vector(kind)], ); } @@ -313,7 +320,11 @@ impl InstructionBuilder<'_, '_> { let free = self.cx.free()?; self.instruction( &[AdapterType::I32, AdapterType::I32], - Instruction::OptionVectorLoad { kind, mem, free }, + Instruction::OptionVectorLoad { + kind: kind.clone(), + mem, + free, + }, &[AdapterType::Vector(kind).option()], ); } @@ -355,7 +366,10 @@ impl InstructionBuilder<'_, '_> { let mem = self.cx.memory()?; self.instruction( &[AdapterType::I32, AdapterType::I32], - Instruction::OptionView { kind, mem }, + Instruction::OptionView { + kind: kind.clone(), + mem, + }, &[AdapterType::Vector(kind).option()], ); } diff --git a/src/cast.rs b/src/cast.rs index f8f0694f02b..b0cfbff04e6 100644 --- a/src/cast.rs +++ b/src/cast.rs @@ -1,4 +1,4 @@ -use crate::JsValue; +use crate::{describe::WasmDescribe, JsValue}; /// A trait for checked and unchecked casting between JS types. /// @@ -154,3 +154,7 @@ where /// won't need to call this. fn unchecked_from_js_ref(val: &JsValue) -> &Self; } + +/// Trait implemented for wrappers around `JsValue`s generated by `#[wasm_bindgen]`. +#[doc(hidden)] +pub trait JsObject: JsCast + WasmDescribe {} diff --git a/src/convert/slices.rs b/src/convert/slices.rs index 0c0fc6267f8..9d0970f4e6a 100644 --- a/src/convert/slices.rs +++ b/src/convert/slices.rs @@ -4,6 +4,7 @@ use std::prelude::v1::*; use core::slice; use core::str; +use crate::cast::JsObject; use crate::convert::OptionIntoWasmAbi; use crate::convert::{FromWasmAbi, IntoWasmAbi, RefFromWasmAbi, RefMutFromWasmAbi, WasmAbi}; use cfg_if::cfg_if; @@ -270,4 +271,41 @@ if_std! { #[inline] fn is_none(slice: &WasmSlice) -> bool { slice.ptr == 0 } } + + impl IntoWasmAbi for Box<[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); + WasmSlice { + ptr: ptr.into_abi(), + len: len as u32, + } + } + } + + impl OptionIntoWasmAbi for Box<[T]> where T: JsObject { + #[inline] + fn none() -> WasmSlice { null_slice() } + } + + impl FromWasmAbi for Box<[T]> where T: JsObject { + type Abi = WasmSlice; + + #[inline] + unsafe fn from_abi(js: WasmSlice) -> Self { + 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(); + return 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/lib.rs b/src/lib.rs index 7b23789a799..6be123ff8e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,7 @@ pub mod convert; pub mod describe; mod cast; -pub use crate::cast::JsCast; +pub use crate::cast::{JsCast, JsObject}; if_std! { extern crate std; diff --git a/tests/wasm/js_objects.js b/tests/wasm/js_objects.js index 96aa7bcfc6b..c131527b843 100644 --- a/tests/wasm/js_objects.js +++ b/tests/wasm/js_objects.js @@ -83,6 +83,18 @@ exports.js_another_vector_return = () => { assert.deepStrictEqual(wasm.another_vector_return_get_array(), [1, 2, 3, 4, 5, 6]); }; +exports.returning_vector_string_foo = () => { + return "This is the mostest awesomest string that can possibly exist."; +}; + +exports.js_returning_vector_string = () => { + assert.strictEqual(wasm.returning_vector_string_bar().length, 10); +}; + +exports.js_another_vector_string_return = () => { + assert.deepStrictEqual(wasm.another_vector_string_return_get_array(), ["1", "2", "3", "4", "5", "6"]); +}; + exports.verify_serde = function(a) { assert.deepStrictEqual(a, { a: 0, diff --git a/tests/wasm/js_objects.rs b/tests/wasm/js_objects.rs index 38263e00a12..445d5329aaa 100644 --- a/tests/wasm/js_objects.rs +++ b/tests/wasm/js_objects.rs @@ -1,3 +1,4 @@ +use js_sys::JsString; use wasm_bindgen::prelude::*; use wasm_bindgen_test::*; @@ -24,8 +25,12 @@ extern "C" { fn returning_vector_foo() -> JsValue; fn js_returning_vector(); - fn js_another_vector_return(); + + fn returning_vector_string_foo() -> JsString; + fn js_returning_vector_string(); + fn js_another_vector_string_return(); + fn verify_serde(val: JsValue) -> JsValue; } @@ -107,6 +112,37 @@ fn another_vector_return() { js_another_vector_return(); } +#[wasm_bindgen] +pub fn returning_vector_string_bar() -> Vec { + let mut res = Vec::new(); + for _ in 0..10 { + res.push(returning_vector_string_foo()) + } + res +} + +#[wasm_bindgen_test] +fn returning_vector_string() { + js_returning_vector_string(); +} + +#[wasm_bindgen] +pub fn another_vector_string_return_get_array() -> Vec { + vec![ + "1".into(), + "2".into(), + "3".into(), + "4".into(), + "5".into(), + "6".into(), + ] +} + +#[wasm_bindgen_test] +fn another_vector_string_return() { + js_another_vector_string_return(); +} + #[cfg(feature = "serde-serialize")] #[wasm_bindgen_test] fn serde() {