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
This commit is contained in:
Alex Crichton 2018-09-17 18:26:45 -07:00
parent ccced83b0e
commit 7cf4213283
17 changed files with 292 additions and 266 deletions

View File

@ -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! {

View File

@ -34,6 +34,7 @@ tys! {
RUST_STRUCT
CHAR
OPTIONAL
UNIT
}
#[derive(Debug)]
@ -61,12 +62,13 @@ pub enum Descriptor {
RustStruct(String),
Char,
Option(Box<Descriptor>),
Unit,
}
#[derive(Debug)]
pub struct Function {
pub arguments: Vec<Descriptor>,
pub ret: Option<Descriptor>,
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::<Vec<_>>();
let ret = if get(data) == 0 {
None
} else {
Some(Descriptor::_decode(data))
};
Function { arguments, ret }
Function {
arguments,
ret: Descriptor::_decode(data),
}
}
}

View File

@ -390,15 +390,12 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
Ok(self)
}
pub fn ret(&mut self, ret: &Option<Descriptor>) -> 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),

View File

@ -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");

View File

@ -285,14 +285,11 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
Ok(())
}
fn ret(&mut self, ret: &Option<Descriptor>) -> 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),

View File

@ -726,13 +726,14 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'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) => {

View File

@ -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<T, JsValue>`](./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)

View File

@ -0,0 +1,20 @@
# `Result<T, JsValue>`
| `T` parameter | `&T` parameter | `&mut T` parameter | `T` return value | `Option<T>` parameter | `Option<T>` 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<T, JsValue>` 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"!.

View File

@ -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<Box<Fn($($var),*)>>,
$($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<Box<Fn($($var),*) -> R>>,
$($var: <$var as FromWasmAbi>::Abi),*
) -> <R as IntoWasmAbi>::Abi {
) -> <R as ReturnWasmAbi>::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<Box<FnMut($($var),*)>>,
$($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<Box<FnMut($($var),*) -> R>>,
$($var: <$var as FromWasmAbi>::Abi),*
) -> <R as IntoWasmAbi>::Abi {
) -> <R as ReturnWasmAbi>::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
}

View File

@ -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),*
) -> <R as IntoWasmAbi>::Abi {
) -> <R as ReturnWasmAbi>::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),*
) -> <R as IntoWasmAbi>::Abi {
) -> <R as ReturnWasmAbi>::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
}
}
}
)*)
}

View File

@ -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<T: OptionFromWasmAbi> FromWasmAbi for Option<T> {
}
}
}
impl IntoWasmAbi for () {
type Abi = ();
fn into_abi(self, _extra: &mut Stack) -> () {
self
}
}
impl<T: IntoWasmAbi> ReturnWasmAbi for Result<T, JsValue> {
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),
}
}
}

View File

@ -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<T: IntoWasmAbi> ReturnWasmAbi for T {
type Abi = T::Abi;
fn return_abi(self, extra: &mut Stack) -> Self::Abi {
self.into_abi(extra)
}
}

View File

@ -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);
<R as WasmDescribe>::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);
<R as WasmDescribe>::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<T: WasmDescribe> WasmDescribe for Option<T> {
T::describe();
}
}
impl WasmDescribe for () {
fn describe() {
inform(UNIT)
}
}
// Note that this is only for `ReturnWasmAbi for Result<T, JsValue>`, which
// throws the result, so we only need to inform about the `T`.
impl<T: WasmDescribe> WasmDescribe for Result<T, JsValue> {
fn describe() {
T::describe()
}
}

View File

@ -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<T: FromWasmAbi + 'static> Deref for JsStatic<T> {
}
}
#[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]

View File

@ -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;

14
tests/wasm/rethrow.js Normal file
View File

@ -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();
};

28
tests/wasm/rethrow.rs Normal file
View File

@ -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<u32, JsValue> {
Err(1.into())
}
#[wasm_bindgen_test]
fn ok_works() {
call_ok();
}
#[wasm_bindgen]
pub fn nothrow() -> Result<u32, JsValue> {
Ok(1)
}