Skip to content

Commit

Permalink
Implement Into<JsValue> for Vec
Browse files Browse the repository at this point in the history
This means that `Vec`s can now be returned from `async` functions.

Fixes rustwasm#3155.

I had to add a new `VectorIntoJsValue` trait, since similarly to
`VectorIntoWasmAbi` the orphan rule won't let us directly implement
`From<Vec<T>>` for `JsValue` outside of `wasm-bindgen`.
  • Loading branch information
Liamolucko committed Mar 24, 2024
1 parent 78463a2 commit 673a371
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 0 deletions.
12 changes: 12 additions & 0 deletions crates/backend/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,12 @@ impl ToTokens for ast::Struct {
#wasm_bindgen::convert::js_value_vector_from_abi(js)
}
}

impl #wasm_bindgen::__rt::VectorIntoJsValue for #name {
fn vector_into_jsvalue(vector: #wasm_bindgen::__rt::std::boxed::Box<[#name]>) -> #wasm_bindgen::JsValue {
#wasm_bindgen::__rt::js_value_vector_into_jsvalue(vector)
}
}
})
.to_tokens(tokens);

Expand Down Expand Up @@ -1509,6 +1515,12 @@ impl ToTokens for ast::Enum {
#wasm_bindgen::convert::js_value_vector_from_abi(js)
}
}

impl #wasm_bindgen::__rt::VectorIntoJsValue for #enum_name {
fn vector_into_jsvalue(vector: #wasm_bindgen::__rt::std::boxed::Box<[#enum_name]>) -> #wasm_bindgen::JsValue {
#wasm_bindgen::__rt::js_value_vector_into_jsvalue(vector)
}
}
})
.to_tokens(into);
}
Expand Down
39 changes: 39 additions & 0 deletions crates/cli-support/src/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,45 @@ intrinsics! {
#[symbol = "__wbindgen_copy_to_typed_array"]
#[signature = fn(slice(U8), ref_externref()) -> Unit]
CopyToTypedArray,
#[symbol = "__wbindgen_uint8_array_new"]
#[signature = fn(slice(U8)) -> Externref]
Uint8ArrayNew,
#[symbol = "__wbindgen_uint8_clamped_array_new"]
#[signature = fn(slice(ClampedU8)) -> Externref]
Uint8ClampedArrayNew,
#[symbol = "__wbindgen_uint16_array_new"]
#[signature = fn(slice(U16)) -> Externref]
Uint16ArrayNew,
#[symbol = "__wbindgen_uint32_array_new"]
#[signature = fn(slice(U32)) -> Externref]
Uint32ArrayNew,
#[symbol = "__wbindgen_biguint64_array_new"]
#[signature = fn(slice(U64)) -> Externref]
BigUint64ArrayNew,
#[symbol = "__wbindgen_int8_array_new"]
#[signature = fn(slice(I8)) -> Externref]
Int8ArrayNew,
#[symbol = "__wbindgen_int16_array_new"]
#[signature = fn(slice(I16)) -> Externref]
Int16ArrayNew,
#[symbol = "__wbindgen_int32_array_new"]
#[signature = fn(slice(I32)) -> Externref]
Int32ArrayNew,
#[symbol = "__wbindgen_bigint64_array_new"]
#[signature = fn(slice(I64)) -> Externref]
BigInt64ArrayNew,
#[symbol = "__wbindgen_float32_array_new"]
#[signature = fn(slice(F32)) -> Externref]
Float32ArrayNew,
#[symbol = "__wbindgen_float64_array_new"]
#[signature = fn(slice(F64)) -> Externref]
Float64ArrayNew,
#[symbol = "__wbindgen_array_new"]
#[signature = fn() -> Externref]
ArrayNew,
#[symbol = "__wbindgen_array_push"]
#[signature = fn(ref_externref(), Externref) -> Unit]
ArrayPush,
#[symbol = "__wbindgen_externref_heap_live_count"]
#[signature = fn() -> I32]
ExternrefHeapLiveCount,
Expand Down
25 changes: 25 additions & 0 deletions crates/cli-support/src/js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3577,6 +3577,31 @@ impl<'a> Context<'a> {
)
}

Intrinsic::Uint8ArrayNew
| Intrinsic::Uint8ClampedArrayNew
| Intrinsic::Uint16ArrayNew
| Intrinsic::Uint32ArrayNew
| Intrinsic::BigUint64ArrayNew
| Intrinsic::Int8ArrayNew
| Intrinsic::Int16ArrayNew
| Intrinsic::Int32ArrayNew
| Intrinsic::BigInt64ArrayNew
| Intrinsic::Float32ArrayNew
| Intrinsic::Float64ArrayNew => {
assert_eq!(args.len(), 1);
args[0].clone()
}

Intrinsic::ArrayNew => {
assert_eq!(args.len(), 0);
"[]".to_string()
}

Intrinsic::ArrayPush => {
assert_eq!(args.len(), 2);
format!("{}.push({})", args[0], args[1])
}

Intrinsic::ExternrefHeapLiveCount => {
assert_eq!(args.len(), 0);
self.expose_global_heap();
Expand Down
106 changes: 106 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,21 @@ externs! {

fn __wbindgen_copy_to_typed_array(ptr: *const u8, len: usize, idx: u32) -> ();

fn __wbindgen_uint8_array_new(ptr: *mut u8, len: usize) -> u32;
fn __wbindgen_uint8_clamped_array_new(ptr: *mut u8, len: usize) -> u32;
fn __wbindgen_uint16_array_new(ptr: *mut u16, len: usize) -> u32;
fn __wbindgen_uint32_array_new(ptr: *mut u32, len: usize) -> u32;
fn __wbindgen_biguint64_array_new(ptr: *mut u64, len: usize) -> u32;
fn __wbindgen_int8_array_new(ptr: *mut i8, len: usize) -> u32;
fn __wbindgen_int16_array_new(ptr: *mut i16, len: usize) -> u32;
fn __wbindgen_int32_array_new(ptr: *mut i32, len: usize) -> u32;
fn __wbindgen_bigint64_array_new(ptr: *mut i64, len: usize) -> u32;
fn __wbindgen_float32_array_new(ptr: *mut f32, len: usize) -> u32;
fn __wbindgen_float64_array_new(ptr: *mut f64, len: usize) -> u32;

fn __wbindgen_array_new() -> u32;
fn __wbindgen_array_push(array: u32, value: u32) -> ();

fn __wbindgen_not(idx: u32) -> u32;

fn __wbindgen_exports() -> u32;
Expand Down Expand Up @@ -1804,6 +1819,35 @@ pub mod __rt {
}
}
}

if_std! {
use core::mem;
use std::boxed::Box;

/// Trait for element types to implement `Into<JsValue>` for vectors of
/// themselves, which isn't possible directly thanks to the orphan rule.
pub trait VectorIntoJsValue: Sized {
fn vector_into_jsvalue(vector: Box<[Self]>) -> JsValue;
}

impl<T: VectorIntoJsValue> From<Box<[T]>> for JsValue {
fn from(vector: Box<[T]>) -> Self {
T::vector_into_jsvalue(vector)
}
}

pub fn js_value_vector_into_jsvalue<T: Into<JsValue>>(vector: Box<[T]>) -> JsValue {
let result = unsafe { JsValue::_new(super::__wbindgen_array_new()) };
for value in vector.into_vec() {
let js: JsValue = value.into();
unsafe { super::__wbindgen_array_push(result.idx, js.idx) }
// `__wbindgen_array_push` takes ownership over `js` and has already dropped it,
// so don't drop it again.
mem::forget(js);
}
result
}
}
}

/// A wrapper type around slices and vectors for binding the `Uint8ClampedArray`
Expand Down Expand Up @@ -1922,3 +1966,65 @@ impl From<JsError> for JsValue {
error.value
}
}

macro_rules! typed_arrays {
($($ty:ident $ctor:ident $clamped_ctor:ident,)*) => {
$(
impl From<Vec<$ty>> for JsValue {
fn from(mut vec: Vec<$ty>) -> Self {
let result = unsafe { JsValue::_new($ctor(vec.as_mut_ptr(), vec.len())) };
mem::forget(vec);
result
}
}

impl From<Clamped<Vec<$ty>>> for JsValue {
fn from(mut vec: Clamped<Vec<$ty>>) -> Self {
let result = unsafe { JsValue::_new($clamped_ctor(vec.as_mut_ptr(), vec.len())) };
mem::forget(vec);
result
}
}
)*
};
}

typed_arrays! {
u8 __wbindgen_uint8_array_new __wbindgen_uint8_clamped_array_new,
u16 __wbindgen_uint16_array_new __wbindgen_uint16_array_new,
u32 __wbindgen_uint32_array_new __wbindgen_uint32_array_new,
u64 __wbindgen_biguint64_array_new __wbindgen_biguint64_array_new,
i8 __wbindgen_int8_array_new __wbindgen_int8_array_new,
i16 __wbindgen_int16_array_new __wbindgen_int16_array_new,
i32 __wbindgen_int32_array_new __wbindgen_int32_array_new,
i64 __wbindgen_bigint64_array_new __wbindgen_bigint64_array_new,
f32 __wbindgen_float32_array_new __wbindgen_float32_array_new,
f64 __wbindgen_float64_array_new __wbindgen_float64_array_new,
}

impl __rt::VectorIntoJsValue for JsValue {
fn vector_into_jsvalue(vector: Box<[JsValue]>) -> JsValue {
__rt::js_value_vector_into_jsvalue::<JsValue>(vector)
}
}

impl<T: JsObject> __rt::VectorIntoJsValue for T {
fn vector_into_jsvalue(vector: Box<[T]>) -> JsValue {
__rt::js_value_vector_into_jsvalue::<T>(vector)
}
}

impl __rt::VectorIntoJsValue for String {
fn vector_into_jsvalue(vector: Box<[String]>) -> JsValue {
__rt::js_value_vector_into_jsvalue::<String>(vector)
}
}

impl<T> From<Vec<T>> for JsValue
where
JsValue: From<Box<[T]>>,
{
fn from(vector: Vec<T>) -> Self {
JsValue::from(vector.into_boxed_slice())
}
}
11 changes: 11 additions & 0 deletions tests/wasm/async_vecs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const wasm = require('wasm-bindgen-test.js');
const assert = require('assert');

exports.js_works = async () => {
assert.deepStrictEqual(await wasm.async_number_vec(), new Int32Array([1, -3, 7, 12]));
assert.deepStrictEqual(await wasm.async_jsvalue_vec(), [1, "hi", new Float64Array(), null]);
assert.deepStrictEqual(await wasm.async_import_vec(), [/hi|bye/, /hello w[a-z]rld/]);
assert.deepStrictEqual(await wasm.async_string_vec(), ["a", "b", "c"]);
assert.strictEqual((await wasm.async_struct_vec()).length, 2);
assert.deepStrictEqual(await wasm.async_enum_vec(), [wasm.AnotherEnum.C, wasm.AnotherEnum.A, wasm.AnotherEnum.B]);
};
66 changes: 66 additions & 0 deletions tests/wasm/async_vecs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;

#[wasm_bindgen(module = "tests/wasm/async_vecs.js")]
extern "C" {
async fn js_works();
}

#[wasm_bindgen]
extern "C" {
pub type RegExp;
#[wasm_bindgen(constructor)]
fn new(re: &str) -> RegExp;
}

#[wasm_bindgen]
pub async fn async_number_vec() -> Vec<i32> {
vec![1, -3, 7, 12]
}

#[wasm_bindgen]
pub async fn async_jsvalue_vec() -> Vec<JsValue> {
vec![
1u32.into(),
"hi".into(),
Vec::<f64>::new().into(),
JsValue::NULL,
]
}

#[wasm_bindgen]
pub async fn async_import_vec() -> Vec<RegExp> {
vec![RegExp::new("hi|bye"), RegExp::new("hello w[a-z]rld")]
}

#[wasm_bindgen]
pub async fn async_string_vec() -> Vec<String> {
vec!["a".to_owned(), "b".to_owned(), "c".to_owned()]
}

#[wasm_bindgen]
#[derive(Clone)]
pub struct AnotherStruct;

#[wasm_bindgen]
pub async fn async_struct_vec() -> Vec<AnotherStruct> {
vec![AnotherStruct; 2]
}

#[wasm_bindgen]
#[derive(Clone)]
pub enum AnotherEnum {
A,
B,
C,
}

#[wasm_bindgen]
pub async fn async_enum_vec() -> Vec<AnotherEnum> {
vec![AnotherEnum::C, AnotherEnum::A, AnotherEnum::B]
}

#[wasm_bindgen_test]
async fn works() {
js_works().await;
}
1 change: 1 addition & 0 deletions tests/wasm/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use wasm_bindgen::prelude::*;

pub mod api;
pub mod arg_names;
pub mod async_vecs;
pub mod bigint;
pub mod char;
pub mod classes;
Expand Down

0 comments on commit 673a371

Please sign in to comment.