From 7cf421328329e60293f94ef036c6aeeef11c84ac Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 17 Sep 2018 18:26:45 -0700 Subject: [PATCH] Allow returning `Result` from functions This commit adds support for exporting a function defined in Rust that returns a `Result`, translating the `Ok` variant to the actual return value and the `Err` variant to an exception that's thrown in JS. The support for return types and descriptors was rejiggered a bit to be a bit more abstract and more well suited for this purpose. We no longer distinguish between functions with a return value and those without a return value. Additionally a new trait, `ReturnWasmAbi`, is used for converting return values. This trait is an internal implementation detail, however, and shouldn't surface itself to users much (if at all). Closes #841 --- crates/backend/src/codegen.rs | 61 ++++++------- crates/cli-support/src/descriptor.rs | 15 ++-- crates/cli-support/src/js/js2rust.rs | 15 ++-- crates/cli-support/src/js/mod.rs | 11 ++- crates/cli-support/src/js/rust2js.rs | 13 ++- crates/macro-support/src/parser.rs | 3 +- guide/src/SUMMARY.md | 1 + guide/src/reference/types/result.md | 20 +++++ src/closure.rs | 129 ++++++++------------------- src/convert/closures.rs | 118 +++++++----------------- src/convert/impls.rs | 23 ++++- src/convert/traits.rs | 23 +++++ src/describe.rs | 39 ++++---- src/lib.rs | 44 ++++++++- tests/wasm/main.rs | 1 + tests/wasm/rethrow.js | 14 +++ tests/wasm/rethrow.rs | 28 ++++++ 17 files changed, 292 insertions(+), 266 deletions(-) create mode 100644 guide/src/reference/types/result.md create mode 100644 tests/wasm/rethrow.js create mode 100644 tests/wasm/rethrow.rs diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 8018c3daa..fe0f9752b 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -418,39 +418,28 @@ impl TryToTokens for ast::Export { } converted_arguments.push(quote! { #ident }); } - let ret_ty; - let convert_ret; - match &self.function.ret { - Some(syn::Type::Reference(_)) => { - bail_span!( - self.function.ret, - "cannot return a borrowed ref with #[wasm_bindgen]", - ) - } - Some(ty) => { - ret_ty = quote! { - -> <#ty as ::wasm_bindgen::convert::IntoWasmAbi>::Abi - }; - convert_ret = quote! { - <#ty as ::wasm_bindgen::convert::IntoWasmAbi> - ::into_abi(#ret, &mut unsafe { - ::wasm_bindgen::convert::GlobalStack::new() - }) - }; - } - None => { - ret_ty = quote!(); - convert_ret = quote!(); - } + let syn_unit = syn::Type::Tuple(syn::TypeTuple { + elems: Default::default(), + paren_token: Default::default(), + }); + let syn_ret = self.function.ret.as_ref().unwrap_or(&syn_unit); + if let syn::Type::Reference(_) = syn_ret { + bail_span!( + syn_ret, + "cannot return a borrowed ref with #[wasm_bindgen]", + ) } - let describe_ret = match &self.function.ret { - Some(ty) => { - quote! { - inform(1); - <#ty as WasmDescribe>::describe(); - } - } - None => quote! { inform(0); }, + let ret_ty = quote! { + -> <#syn_ret as ::wasm_bindgen::convert::ReturnWasmAbi>::Abi + }; + let convert_ret = quote! { + <#syn_ret as ::wasm_bindgen::convert::ReturnWasmAbi> + ::return_abi(#ret, &mut unsafe { + ::wasm_bindgen::convert::GlobalStack::new() + }) + }; + let describe_ret = quote! { + <#syn_ret as WasmDescribe>::describe(); }; let nargs = self.function.arguments.len() as u32; let argtys = self.function.arguments.iter().map(|arg| &arg.ty); @@ -464,6 +453,10 @@ impl TryToTokens for ast::Export { pub extern fn #generated_name(#(#args),*) #ret_ty { // See definition of `link_mem_intrinsics` for what this is doing ::wasm_bindgen::__rt::link_mem_intrinsics(); + + // Scope all local variables to be destroyed after we call the + // function to ensure that `#convert_ret`, if it panics, doesn't + // leak anything. let #ret = { let mut __stack = unsafe { ::wasm_bindgen::convert::GlobalStack::new() @@ -954,8 +947,8 @@ impl<'a> ToTokens for DescribeImport<'a> { let argtys = f.function.arguments.iter().map(|arg| &arg.ty); let nargs = f.function.arguments.len() as u32; let inform_ret = match &f.js_ret { - Some(ref t) => quote! { inform(1); <#t as WasmDescribe>::describe(); }, - None => quote! { inform(0); }, + Some(ref t) => quote! { <#t as WasmDescribe>::describe(); }, + None => quote! { <() as WasmDescribe>::describe(); }, }; Descriptor(&f.shim, quote! { diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index 2a091cde6..3b87f07dc 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -34,6 +34,7 @@ tys! { RUST_STRUCT CHAR OPTIONAL + UNIT } #[derive(Debug)] @@ -61,12 +62,13 @@ pub enum Descriptor { RustStruct(String), Char, Option(Box), + Unit, } #[derive(Debug)] pub struct Function { pub arguments: Vec, - pub ret: Option, + pub ret: Descriptor, } #[derive(Debug)] @@ -128,6 +130,7 @@ impl Descriptor { Descriptor::RustStruct(name) } CHAR => Descriptor::Char, + UNIT => Descriptor::Unit, other => panic!("unknown descriptor: {}", other), } } @@ -295,12 +298,10 @@ impl Function { let arguments = (0..get(data)) .map(|_| Descriptor::_decode(data)) .collect::>(); - let ret = if get(data) == 0 { - None - } else { - Some(Descriptor::_decode(data)) - }; - Function { arguments, ret } + Function { + arguments, + ret: Descriptor::_decode(data), + } } } diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index 57621ec8d..35035d8cf 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -390,15 +390,12 @@ impl<'a, 'b> Js2Rust<'a, 'b> { Ok(self) } - pub fn ret(&mut self, ret: &Option) -> Result<&mut Self, Error> { - let ty = match *ret { - Some(ref t) => t, - None => { - self.ret_ty = "void".to_string(); - self.ret_expr = format!("return RET;"); - return Ok(self); - } - }; + pub fn ret(&mut self, ty: &Descriptor) -> Result<&mut Self, Error> { + if let Descriptor::Unit = ty { + self.ret_ty = "void".to_string(); + self.ret_expr = format!("return RET;"); + return Ok(self); + } let (ty, optional) = match ty { Descriptor::Option(t) => (&**t, true), diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index e1a9175db..1bd0b5b01 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -393,6 +393,11 @@ impl<'a> Context<'a> { )) })?; + self.bind("__wbindgen_rethrow", &|me| { + me.expose_take_object(); + Ok(String::from("function(idx) { throw takeObject(idx); }")) + })?; + self.create_memory_export(); self.unexport_unused_internal_exports(); closures::rewrite(self)?; @@ -626,7 +631,9 @@ impl<'a> Context<'a> { let set = { let mut cx = Js2Rust::new(&field.name, self); - cx.method(true, false).argument(&descriptor)?.ret(&None)?; + cx.method(true, false) + .argument(&descriptor)? + .ret(&Descriptor::Unit)?; ts_dst.push_str(&format!( "{}{}: {}\n", if field.readonly { "readonly " } else { "" }, @@ -637,7 +644,7 @@ impl<'a> Context<'a> { }; let (get, _ts, js_doc) = Js2Rust::new(&field.name, self) .method(true, false) - .ret(&Some(descriptor))? + .ret(&descriptor)? .finish("", &format!("wasm.{}", wasm_getter)); if !dst.ends_with("\n") { dst.push_str("\n"); diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index 031a0b204..afbdfd017 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -285,14 +285,11 @@ impl<'a, 'b> Rust2Js<'a, 'b> { Ok(()) } - fn ret(&mut self, ret: &Option) -> Result<(), Error> { - let ty = match *ret { - Some(ref t) => t, - None => { - self.ret_expr = "JS;".to_string(); - return Ok(()); - } - }; + fn ret(&mut self, ty: &Descriptor) -> Result<(), Error> { + if let Descriptor::Unit = ty { + self.ret_expr = "JS;".to_string(); + return Ok(()); + } let (ty, optional) = match ty { Descriptor::Option(t) => (&**t, true), _ => (ty, false), diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index dbc63d32b..968f04c77 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -726,13 +726,14 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { } let comments = extract_doc_comments(&f.attrs); f.to_tokens(tokens); + let opts = opts.unwrap_or_default(); program.exports.push(ast::Export { class: None, method_self: None, constructor: None, comments, rust_name: f.ident.clone(), - function: f.convert(opts.unwrap_or_default())?, + function: f.convert(opts)?, }); } syn::Item::Struct(mut s) => { diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 2bbd6a330..97069f784 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -30,6 +30,7 @@ - [`String`](./reference/types/string.md) - [Number Slices](./reference/types/number-slices.md) - [Boxed Number Slices](./reference/types/boxed-number-slices.md) + - [`Result`](./reference/types/result.md) - [`#[wasm_bindgen]` Attributes](./reference/attributes/index.md) - [On JavaScript Imports](./reference/attributes/on-js-imports/index.md) - [`catch`](./reference/attributes/on-js-imports/catch.md) diff --git a/guide/src/reference/types/result.md b/guide/src/reference/types/result.md new file mode 100644 index 000000000..1ffae1a47 --- /dev/null +++ b/guide/src/reference/types/result.md @@ -0,0 +1,20 @@ +# `Result` + +| `T` parameter | `&T` parameter | `&mut T` parameter | `T` return value | `Option` parameter | `Option` return value | JavaScript representation | +|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| No | No | No | No | No | Yes | Same as `T`, or an exception | + +The `Result` type can be returned from functions exported to JS as well as +closures in Rust. Only `Result` is supported where `T` can be +converted to JS. Whenever `Ok(val)` is encountered it's converted to JS and +handed off, and whenever `Err(error)` is encountered an exception is thrown in +JS with `error`. + +You can use `Result` to enable handling of JS exceptions with `?` in Rust, +naturally propagating it upwards to the wasm boundary. Furthermore you can also +return custom types in Rust so long as they're all convertible to `JsValue`. + +Note that if you import a JS function with `Result` you need +`#[wasm_bindgen(catch)]` to be annotated on the import (unlike exported +functions, which require no extra annotation). This may not be necessary in the +future though and it may work "as is"!. diff --git a/src/closure.rs b/src/closure.rs index ddee882f2..e67c3e897 100644 --- a/src/closure.rs +++ b/src/closure.rs @@ -14,7 +14,7 @@ use std::rc::Rc; use JsValue; use convert::*; use describe::*; -use throw; +use throw_str; /// A handle to both a closure in Rust as well as JS closure which will invoke /// the Rust closure. @@ -304,41 +304,9 @@ macro_rules! doit { ($( ($($var:ident)*) )*) => ($( - // Fn with no return - unsafe impl<$($var),*> WasmClosure for Fn($($var),*) - where $($var: FromWasmAbi + 'static,)* - { - fn describe() { - <&Self>::describe(); - } - - fn invoke_fn() -> u32 { - #[allow(non_snake_case)] - unsafe extern fn invoke<$($var: FromWasmAbi,)*>( - a: *const UnsafeCell>, - $($var: <$var as FromWasmAbi>::Abi),* - ) { - if a.is_null() { - throw("closure invoked recursively or destroyed already"); - } - let a = Rc::from_raw(a); - let my_handle = a.clone(); - drop(Rc::into_raw(a)); - let f: &Fn($($var),*) = &**my_handle.get(); - let mut _stack = GlobalStack::new(); - $( - let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); - )* - f($($var),*) - } - invoke::<$($var,)*> as u32 - } - } - - // Fn with no return unsafe impl<$($var,)* R> WasmClosure for Fn($($var),*) -> R where $($var: FromWasmAbi + 'static,)* - R: IntoWasmAbi + 'static, + R: ReturnWasmAbi + 'static, { fn describe() { <&Self>::describe(); @@ -346,61 +314,36 @@ macro_rules! doit { fn invoke_fn() -> u32 { #[allow(non_snake_case)] - unsafe extern fn invoke<$($var: FromWasmAbi,)* R: IntoWasmAbi>( + unsafe extern fn invoke<$($var: FromWasmAbi,)* R: ReturnWasmAbi>( a: *const UnsafeCell R>>, $($var: <$var as FromWasmAbi>::Abi),* - ) -> ::Abi { + ) -> ::Abi { if a.is_null() { - throw("closure invoked recursively or destroyed already"); + throw_str("closure invoked recursively or destroyed already"); } - let a = Rc::from_raw(a); - let my_handle = a.clone(); - drop(Rc::into_raw(a)); - let f: &Fn($($var),*) -> R = &**my_handle.get(); - let mut _stack = GlobalStack::new(); - $( - let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); - )* - f($($var),*).into_abi(&mut GlobalStack::new()) + // Make sure all stack variables are converted before we + // convert `ret` as it may throw (for `Result`, for + // example) + let ret = { + let a = Rc::from_raw(a); + let my_handle = a.clone(); + drop(Rc::into_raw(a)); + let f: &Fn($($var),*) -> R = &**my_handle.get(); + let mut _stack = GlobalStack::new(); + $( + let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); + )* + f($($var),*) + }; + ret.return_abi(&mut GlobalStack::new()) } invoke::<$($var,)* R> as u32 } } - // FnMut with no return - unsafe impl<$($var),*> WasmClosure for FnMut($($var),*) - where $($var: FromWasmAbi + 'static,)* - { - fn describe() { - <&mut Self>::describe(); - } - fn invoke_fn() -> u32 { - #[allow(non_snake_case)] - unsafe extern fn invoke<$($var: FromWasmAbi,)*>( - a: *const UnsafeCell>, - $($var: <$var as FromWasmAbi>::Abi),* - ) { - if a.is_null() { - throw("closure invoked recursively or destroyed already"); - } - let a = Rc::from_raw(a); - let my_handle = a.clone(); - drop(Rc::into_raw(a)); - let f: &mut FnMut($($var),*) = &mut **my_handle.get(); - let mut _stack = GlobalStack::new(); - $( - let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); - )* - f($($var),*) - } - invoke::<$($var,)*> as u32 - } - } - - // Fn with no return unsafe impl<$($var,)* R> WasmClosure for FnMut($($var),*) -> R where $($var: FromWasmAbi + 'static,)* - R: IntoWasmAbi + 'static, + R: ReturnWasmAbi + 'static, { fn describe() { <&mut Self>::describe(); @@ -408,22 +351,28 @@ macro_rules! doit { fn invoke_fn() -> u32 { #[allow(non_snake_case)] - unsafe extern fn invoke<$($var: FromWasmAbi,)* R: IntoWasmAbi>( + unsafe extern fn invoke<$($var: FromWasmAbi,)* R: ReturnWasmAbi>( a: *const UnsafeCell R>>, $($var: <$var as FromWasmAbi>::Abi),* - ) -> ::Abi { + ) -> ::Abi { if a.is_null() { - throw("closure invoked recursively or destroyed already"); + throw_str("closure invoked recursively or destroyed already"); } - let a = Rc::from_raw(a); - let my_handle = a.clone(); - drop(Rc::into_raw(a)); - let f: &mut FnMut($($var),*) -> R = &mut **my_handle.get(); - let mut _stack = GlobalStack::new(); - $( - let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); - )* - f($($var),*).into_abi(&mut GlobalStack::new()) + // Make sure all stack variables are converted before we + // convert `ret` as it may throw (for `Result`, for + // example) + let ret = { + let a = Rc::from_raw(a); + let my_handle = a.clone(); + drop(Rc::into_raw(a)); + let f: &mut FnMut($($var),*) -> R = &mut **my_handle.get(); + let mut _stack = GlobalStack::new(); + $( + let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); + )* + f($($var),*) + }; + ret.return_abi(&mut GlobalStack::new()) } invoke::<$($var,)* R> as u32 } diff --git a/src/convert/closures.rs b/src/convert/closures.rs index 4bbcae2f7..3cc0ef1cb 100644 --- a/src/convert/closures.rs +++ b/src/convert/closures.rs @@ -1,34 +1,37 @@ -#![allow(const_err)] // FIXME(rust-lang/rust#52603) - use core::mem; -use convert::{FromWasmAbi, IntoWasmAbi, GlobalStack, Stack}; -use throw; +use convert::{FromWasmAbi, IntoWasmAbi, GlobalStack, Stack, ReturnWasmAbi}; +use throw_str; macro_rules! stack_closures { ($( ($($var:ident)*) )*) => ($( impl<'a, 'b, $($var,)* R> IntoWasmAbi for &'a (Fn($($var),*) -> R + 'b) where $($var: FromWasmAbi,)* - R: IntoWasmAbi + R: ReturnWasmAbi { type Abi = u32; fn into_abi(self, extra: &mut Stack) -> u32 { #[allow(non_snake_case)] - unsafe extern fn invoke<$($var: FromWasmAbi,)* R: IntoWasmAbi>( + unsafe extern fn invoke<$($var: FromWasmAbi,)* R: ReturnWasmAbi>( a: usize, b: usize, $($var: <$var as FromWasmAbi>::Abi),* - ) -> ::Abi { + ) -> ::Abi { if a == 0 { - throw("closure invoked recursively or destroyed already"); + throw_str("closure invoked recursively or destroyed already"); } - let f: &Fn($($var),*) -> R = mem::transmute((a, b)); - let mut _stack = GlobalStack::new(); - $( - let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); - )* - f($($var),*).into_abi(&mut GlobalStack::new()) + // Scope all local variables before we call `return_abi` to + // ensure they're all destroyed as `return_abi` may throw + let ret = { + let f: &Fn($($var),*) -> R = mem::transmute((a, b)); + let mut _stack = GlobalStack::new(); + $( + let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); + )* + f($($var),*) + }; + ret.return_abi(&mut GlobalStack::new()) } unsafe { let (a, b): (usize, usize) = mem::transmute(self); @@ -39,59 +42,33 @@ macro_rules! stack_closures { } } - impl<'a, 'b, $($var,)*> IntoWasmAbi for &'a (Fn($($var),*) + 'b) - where $($var: FromWasmAbi,)* - { - type Abi = u32; - - fn into_abi(self, extra: &mut Stack) -> u32 { - #[allow(non_snake_case)] - unsafe extern fn invoke<$($var: FromWasmAbi,)* >( - a: usize, - b: usize, - $($var: <$var as FromWasmAbi>::Abi),* - ) { - if a == 0 { - throw("closure invoked recursively or destroyed already"); - } - let f: &Fn($($var),*) = mem::transmute((a, b)); - let mut _stack = GlobalStack::new(); - $( - let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); - )* - f($($var),*) - } - unsafe { - let (a, b): (usize, usize) = mem::transmute(self); - extra.push(a as u32); - extra.push(b as u32); - invoke::<$($var,)*> as u32 - } - } - } - impl<'a, 'b, $($var,)* R> IntoWasmAbi for &'a mut (FnMut($($var),*) -> R + 'b) where $($var: FromWasmAbi,)* - R: IntoWasmAbi + R: ReturnWasmAbi { type Abi = u32; fn into_abi(self, extra: &mut Stack) -> u32 { #[allow(non_snake_case)] - unsafe extern fn invoke<$($var: FromWasmAbi,)* R: IntoWasmAbi>( + unsafe extern fn invoke<$($var: FromWasmAbi,)* R: ReturnWasmAbi>( a: usize, b: usize, $($var: <$var as FromWasmAbi>::Abi),* - ) -> ::Abi { + ) -> ::Abi { if a == 0 { - throw("closure invoked recursively or destroyed already"); + throw_str("closure invoked recursively or destroyed already"); } - let f: &mut FnMut($($var),*) -> R = mem::transmute((a, b)); - let mut _stack = GlobalStack::new(); - $( - let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); - )* - f($($var),*).into_abi(&mut GlobalStack::new()) + // Scope all local variables before we call `return_abi` to + // ensure they're all destroyed as `return_abi` may throw + let ret = { + let f: &mut FnMut($($var),*) -> R = mem::transmute((a, b)); + let mut _stack = GlobalStack::new(); + $( + let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); + )* + f($($var),*) + }; + ret.return_abi(&mut GlobalStack::new()) } unsafe { let (a, b): (usize, usize) = mem::transmute(self); @@ -101,37 +78,6 @@ macro_rules! stack_closures { } } } - - impl<'a, 'b, $($var,)*> IntoWasmAbi for &'a mut (FnMut($($var),*) + 'b) - where $($var: FromWasmAbi,)* - { - type Abi = u32; - - fn into_abi(self, extra: &mut Stack) -> u32 { - #[allow(non_snake_case)] - unsafe extern fn invoke<$($var: FromWasmAbi,)* >( - a: usize, - b: usize, - $($var: <$var as FromWasmAbi>::Abi),* - ) { - if a == 0 { - throw("closure invoked recursively or destroyed already"); - } - let f: &mut FnMut($($var),*) = mem::transmute((a, b)); - let mut _stack = GlobalStack::new(); - $( - let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); - )* - f($($var),*) - } - unsafe { - let (a, b): (usize, usize) = mem::transmute(self); - extra.push(a as u32); - extra.push(b as u32); - invoke::<$($var,)*> as u32 - } - } - } )*) } diff --git a/src/convert/impls.rs b/src/convert/impls.rs index 82e68de37..2613816b0 100644 --- a/src/convert/impls.rs +++ b/src/convert/impls.rs @@ -2,10 +2,12 @@ use core::char; use core::mem::{self, ManuallyDrop}; use convert::{Stack, FromWasmAbi, IntoWasmAbi, RefFromWasmAbi}; -use convert::{OptionIntoWasmAbi, OptionFromWasmAbi}; +use convert::{OptionIntoWasmAbi, OptionFromWasmAbi, ReturnWasmAbi}; use convert::traits::WasmAbi; use JsValue; +unsafe impl WasmAbi for () {} + #[repr(C)] pub struct WasmOptionalI32 { pub present: u32, @@ -370,3 +372,22 @@ impl FromWasmAbi for Option { } } } + +impl IntoWasmAbi for () { + type Abi = (); + + fn into_abi(self, _extra: &mut Stack) -> () { + self + } +} + +impl ReturnWasmAbi for Result { + type Abi = T::Abi; + + fn return_abi(self, extra: &mut Stack) -> Self::Abi { + match self { + Ok(v) => v.into_abi(extra), + Err(e) => ::throw_val(e), + } + } +} diff --git a/src/convert/traits.rs b/src/convert/traits.rs index c3664168d..b795b9612 100644 --- a/src/convert/traits.rs +++ b/src/convert/traits.rs @@ -106,3 +106,26 @@ unsafe impl WasmAbi for u32 {} unsafe impl WasmAbi for i32 {} unsafe impl WasmAbi for f32 {} unsafe impl WasmAbi for f64 {} + +/// A trait representing how to interepret the return value of a function for +/// the wasm ABI. +/// +/// This is very similar to the `IntoWasmAbi` trait and in fact has a blanket +/// implementation for all implementors of the `IntoWasmAbi`. The primary use +/// case of this trait is to enable functions to return `Result`, interpreting +/// an error as "rethrow this to JS" +pub trait ReturnWasmAbi: WasmDescribe { + /// Same as `IntoWasmAbi::Abi` + type Abi: WasmAbi; + + /// Same as `IntoWasmAbi::into_abi`, except that it may throw and never + /// return in the case of `Err`. + fn return_abi(self, extra: &mut Stack) -> Self::Abi; +} + +impl ReturnWasmAbi for T { + type Abi = T::Abi; + fn return_abi(self, extra: &mut Stack) -> Self::Abi { + self.into_abi(extra) + } +} diff --git a/src/describe.rs b/src/describe.rs index cbc0f7c9f..4a1b747e5 100644 --- a/src/describe.rs +++ b/src/describe.rs @@ -39,6 +39,7 @@ tys! { RUST_STRUCT CHAR OPTIONAL + UNIT } #[inline(always)] // see `interpret.rs` in the the cli-support crate @@ -152,22 +153,10 @@ macro_rules! doit { inform(FUNCTION); inform(cnt!($($var)*)); $(<$var as WasmDescribe>::describe();)* - inform(1); ::describe(); } } - impl<'a, $($var,)* > WasmDescribe for Fn($($var),*) + 'a - where $($var: WasmDescribe,)* - { - fn describe() { - inform(FUNCTION); - inform(cnt!($($var)*)); - $(<$var as WasmDescribe>::describe();)* - inform(0); - } - } - impl<'a, $($var,)* R> WasmDescribe for FnMut($($var),*) -> R + 'a where $($var: WasmDescribe,)* R: WasmDescribe @@ -176,21 +165,9 @@ macro_rules! doit { inform(FUNCTION); inform(cnt!($($var)*)); $(<$var as WasmDescribe>::describe();)* - inform(1); ::describe(); } } - - impl<'a, $($var,)* > WasmDescribe for FnMut($($var),*) + 'a - where $($var: WasmDescribe,)* - { - fn describe() { - inform(FUNCTION); - inform(cnt!($($var)*)); - $(<$var as WasmDescribe>::describe();)* - inform(0); - } - } )*) } @@ -211,3 +188,17 @@ impl WasmDescribe for Option { T::describe(); } } + +impl WasmDescribe for () { + fn describe() { + inform(UNIT) + } +} + +// Note that this is only for `ReturnWasmAbi for Result`, which +// throws the result, so we only need to inform about the `T`. +impl WasmDescribe for Result { + fn describe() { + T::describe() + } +} diff --git a/src/lib.rs b/src/lib.rs index 79dcef457..90fd896c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ extern crate wasm_bindgen_macro; use core::cell::UnsafeCell; use core::fmt; +use core::mem; use core::ops::Deref; use core::ptr; @@ -428,6 +429,7 @@ externs! { fn __wbindgen_is_string(idx: u32) -> u32; fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8; fn __wbindgen_throw(a: *const u8, b: usize) -> !; + fn __wbindgen_rethrow(a: u32) -> !; fn __wbindgen_cb_drop(idx: u32) -> (); fn __wbindgen_cb_forget(idx: u32) -> (); @@ -552,19 +554,53 @@ impl Deref for JsStatic { } } +#[cold] +#[inline(never)] +#[deprecated(note = "renamed to `throw_str`")] +#[doc(hidden)] +pub fn throw(s: &str) -> ! { + throw_str(s) +} + + /// Throws a JS exception. /// /// This function will throw a JS exception with the message provided. The /// function will not return as the wasm stack will be popped when the exception /// is thrown. +/// +/// Note that it is very easy to leak memory with this function because this +/// function, unlike `panic!` on other platforms, **will not run destructors**. +/// It's recommended to return a `Result` where possible to avoid the worry of +/// leaks. #[cold] #[inline(never)] -pub fn throw(s: &str) -> ! { +pub fn throw_str(s: &str) -> ! { unsafe { __wbindgen_throw(s.as_ptr(), s.len()); } } +/// Rethrow a JS exception +/// +/// This function will throw a JS exception with the JS value provided. This +/// function will not return and the wasm stack will be popped until the point +/// of entry of wasm itself. +/// +/// Note that it is very easy to leak memory with this function because this +/// function, unlike `panic!` on other platforms, **will not run destructors**. +/// It's recommended to return a `Result` where possible to avoid the worry of +/// leaks. +#[cold] +#[inline(never)] +pub fn throw_val(s: JsValue) -> ! { + unsafe { + let idx = s.idx; + mem::forget(s); + __wbindgen_rethrow(idx); + } +} + /// Returns a handle to this wasm instance's `WebAssembly.Memory` pub fn memory() -> JsValue { unsafe { @@ -604,7 +640,7 @@ pub mod __rt { #[cold] #[inline(never)] fn throw_null() -> ! { - super::throw("null pointer passed to rust"); + super::throw_str("null pointer passed to rust"); } /// A vendored version of `RefCell` from the standard library. @@ -726,7 +762,7 @@ pub mod __rt { } fn borrow_fail() -> ! { - super::throw( + super::throw_str( "recursive use of an object detected which would lead to \ unsafe aliasing in rust", ); @@ -748,7 +784,7 @@ pub mod __rt { } } - super::throw("invalid malloc request"); + super::throw_str("invalid malloc request"); } #[no_mangle] diff --git a/tests/wasm/main.rs b/tests/wasm/main.rs index f101fe4a4..5321bf870 100644 --- a/tests/wasm/main.rs +++ b/tests/wasm/main.rs @@ -26,6 +26,7 @@ pub mod math; pub mod node; pub mod option; pub mod optional_primitives; +pub mod rethrow; pub mod simple; pub mod slice; pub mod structural; diff --git a/tests/wasm/rethrow.js b/tests/wasm/rethrow.js new file mode 100644 index 000000000..70e9c3381 --- /dev/null +++ b/tests/wasm/rethrow.js @@ -0,0 +1,14 @@ +const wasm = require('wasm-bindgen-test.js'); +const assert = require('assert'); + +exports.call_throw_one = function() { + try { + wasm.throw_one(); + } catch (e) { + assert.strictEqual(e, 1); + } +}; + +exports.call_ok = function() { + wasm.nothrow(); +}; diff --git a/tests/wasm/rethrow.rs b/tests/wasm/rethrow.rs new file mode 100644 index 000000000..7cee77191 --- /dev/null +++ b/tests/wasm/rethrow.rs @@ -0,0 +1,28 @@ +use wasm_bindgen_test::*; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(module = "tests/wasm/rethrow.js")] +extern { + fn call_throw_one(); + fn call_ok(); +} + +#[wasm_bindgen_test] +fn err_works() { + call_throw_one(); +} + +#[wasm_bindgen] +pub fn throw_one() -> Result { + Err(1.into()) +} + +#[wasm_bindgen_test] +fn ok_works() { + call_ok(); +} + +#[wasm_bindgen] +pub fn nothrow() -> Result { + Ok(1) +}