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 }); converted_arguments.push(quote! { #ident });
} }
let ret_ty; let syn_unit = syn::Type::Tuple(syn::TypeTuple {
let convert_ret; elems: Default::default(),
match &self.function.ret { paren_token: Default::default(),
Some(syn::Type::Reference(_)) => { });
bail_span!( let syn_ret = self.function.ret.as_ref().unwrap_or(&syn_unit);
self.function.ret, if let syn::Type::Reference(_) = syn_ret {
"cannot return a borrowed ref with #[wasm_bindgen]", bail_span!(
) syn_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 describe_ret = match &self.function.ret { let ret_ty = quote! {
Some(ty) => { -> <#syn_ret as ::wasm_bindgen::convert::ReturnWasmAbi>::Abi
quote! { };
inform(1); let convert_ret = quote! {
<#ty as WasmDescribe>::describe(); <#syn_ret as ::wasm_bindgen::convert::ReturnWasmAbi>
} ::return_abi(#ret, &mut unsafe {
} ::wasm_bindgen::convert::GlobalStack::new()
None => quote! { inform(0); }, })
};
let describe_ret = quote! {
<#syn_ret as WasmDescribe>::describe();
}; };
let nargs = self.function.arguments.len() as u32; let nargs = self.function.arguments.len() as u32;
let argtys = self.function.arguments.iter().map(|arg| &arg.ty); 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 { pub extern fn #generated_name(#(#args),*) #ret_ty {
// See definition of `link_mem_intrinsics` for what this is doing // See definition of `link_mem_intrinsics` for what this is doing
::wasm_bindgen::__rt::link_mem_intrinsics(); ::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 #ret = {
let mut __stack = unsafe { let mut __stack = unsafe {
::wasm_bindgen::convert::GlobalStack::new() ::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 argtys = f.function.arguments.iter().map(|arg| &arg.ty);
let nargs = f.function.arguments.len() as u32; let nargs = f.function.arguments.len() as u32;
let inform_ret = match &f.js_ret { let inform_ret = match &f.js_ret {
Some(ref t) => quote! { inform(1); <#t as WasmDescribe>::describe(); }, Some(ref t) => quote! { <#t as WasmDescribe>::describe(); },
None => quote! { inform(0); }, None => quote! { <() as WasmDescribe>::describe(); },
}; };
Descriptor(&f.shim, quote! { Descriptor(&f.shim, quote! {

View File

@ -34,6 +34,7 @@ tys! {
RUST_STRUCT RUST_STRUCT
CHAR CHAR
OPTIONAL OPTIONAL
UNIT
} }
#[derive(Debug)] #[derive(Debug)]
@ -61,12 +62,13 @@ pub enum Descriptor {
RustStruct(String), RustStruct(String),
Char, Char,
Option(Box<Descriptor>), Option(Box<Descriptor>),
Unit,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Function { pub struct Function {
pub arguments: Vec<Descriptor>, pub arguments: Vec<Descriptor>,
pub ret: Option<Descriptor>, pub ret: Descriptor,
} }
#[derive(Debug)] #[derive(Debug)]
@ -128,6 +130,7 @@ impl Descriptor {
Descriptor::RustStruct(name) Descriptor::RustStruct(name)
} }
CHAR => Descriptor::Char, CHAR => Descriptor::Char,
UNIT => Descriptor::Unit,
other => panic!("unknown descriptor: {}", other), other => panic!("unknown descriptor: {}", other),
} }
} }
@ -295,12 +298,10 @@ impl Function {
let arguments = (0..get(data)) let arguments = (0..get(data))
.map(|_| Descriptor::_decode(data)) .map(|_| Descriptor::_decode(data))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let ret = if get(data) == 0 { Function {
None arguments,
} else { ret: Descriptor::_decode(data),
Some(Descriptor::_decode(data)) }
};
Function { arguments, ret }
} }
} }

View File

@ -390,15 +390,12 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
Ok(self) Ok(self)
} }
pub fn ret(&mut self, ret: &Option<Descriptor>) -> Result<&mut Self, Error> { pub fn ret(&mut self, ty: &Descriptor) -> Result<&mut Self, Error> {
let ty = match *ret { if let Descriptor::Unit = ty {
Some(ref t) => t, self.ret_ty = "void".to_string();
None => { self.ret_expr = format!("return RET;");
self.ret_ty = "void".to_string(); return Ok(self);
self.ret_expr = format!("return RET;"); }
return Ok(self);
}
};
let (ty, optional) = match ty { let (ty, optional) = match ty {
Descriptor::Option(t) => (&**t, true), 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.create_memory_export();
self.unexport_unused_internal_exports(); self.unexport_unused_internal_exports();
closures::rewrite(self)?; closures::rewrite(self)?;
@ -626,7 +631,9 @@ impl<'a> Context<'a> {
let set = { let set = {
let mut cx = Js2Rust::new(&field.name, self); 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!( ts_dst.push_str(&format!(
"{}{}: {}\n", "{}{}: {}\n",
if field.readonly { "readonly " } else { "" }, if field.readonly { "readonly " } else { "" },
@ -637,7 +644,7 @@ impl<'a> Context<'a> {
}; };
let (get, _ts, js_doc) = Js2Rust::new(&field.name, self) let (get, _ts, js_doc) = Js2Rust::new(&field.name, self)
.method(true, false) .method(true, false)
.ret(&Some(descriptor))? .ret(&descriptor)?
.finish("", &format!("wasm.{}", wasm_getter)); .finish("", &format!("wasm.{}", wasm_getter));
if !dst.ends_with("\n") { if !dst.ends_with("\n") {
dst.push_str("\n"); dst.push_str("\n");

View File

@ -285,14 +285,11 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
Ok(()) Ok(())
} }
fn ret(&mut self, ret: &Option<Descriptor>) -> Result<(), Error> { fn ret(&mut self, ty: &Descriptor) -> Result<(), Error> {
let ty = match *ret { if let Descriptor::Unit = ty {
Some(ref t) => t, self.ret_expr = "JS;".to_string();
None => { return Ok(());
self.ret_expr = "JS;".to_string(); }
return Ok(());
}
};
let (ty, optional) = match ty { let (ty, optional) = match ty {
Descriptor::Option(t) => (&**t, true), Descriptor::Option(t) => (&**t, true),
_ => (ty, false), _ => (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); let comments = extract_doc_comments(&f.attrs);
f.to_tokens(tokens); f.to_tokens(tokens);
let opts = opts.unwrap_or_default();
program.exports.push(ast::Export { program.exports.push(ast::Export {
class: None, class: None,
method_self: None, method_self: None,
constructor: None, constructor: None,
comments, comments,
rust_name: f.ident.clone(), rust_name: f.ident.clone(),
function: f.convert(opts.unwrap_or_default())?, function: f.convert(opts)?,
}); });
} }
syn::Item::Struct(mut s) => { syn::Item::Struct(mut s) => {

View File

@ -30,6 +30,7 @@
- [`String`](./reference/types/string.md) - [`String`](./reference/types/string.md)
- [Number Slices](./reference/types/number-slices.md) - [Number Slices](./reference/types/number-slices.md)
- [Boxed Number Slices](./reference/types/boxed-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) - [`#[wasm_bindgen]` Attributes](./reference/attributes/index.md)
- [On JavaScript Imports](./reference/attributes/on-js-imports/index.md) - [On JavaScript Imports](./reference/attributes/on-js-imports/index.md)
- [`catch`](./reference/attributes/on-js-imports/catch.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 JsValue;
use convert::*; use convert::*;
use describe::*; use describe::*;
use throw; use throw_str;
/// A handle to both a closure in Rust as well as JS closure which will invoke /// A handle to both a closure in Rust as well as JS closure which will invoke
/// the Rust closure. /// the Rust closure.
@ -304,41 +304,9 @@ macro_rules! doit {
($( ($(
($($var:ident)*) ($($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 unsafe impl<$($var,)* R> WasmClosure for Fn($($var),*) -> R
where $($var: FromWasmAbi + 'static,)* where $($var: FromWasmAbi + 'static,)*
R: IntoWasmAbi + 'static, R: ReturnWasmAbi + 'static,
{ {
fn describe() { fn describe() {
<&Self>::describe(); <&Self>::describe();
@ -346,61 +314,36 @@ macro_rules! doit {
fn invoke_fn() -> u32 { fn invoke_fn() -> u32 {
#[allow(non_snake_case)] #[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>>, a: *const UnsafeCell<Box<Fn($($var),*) -> R>>,
$($var: <$var as FromWasmAbi>::Abi),* $($var: <$var as FromWasmAbi>::Abi),*
) -> <R as IntoWasmAbi>::Abi { ) -> <R as ReturnWasmAbi>::Abi {
if a.is_null() { 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); // Make sure all stack variables are converted before we
let my_handle = a.clone(); // convert `ret` as it may throw (for `Result`, for
drop(Rc::into_raw(a)); // example)
let f: &Fn($($var),*) -> R = &**my_handle.get(); let ret = {
let mut _stack = GlobalStack::new(); let a = Rc::from_raw(a);
$( let my_handle = a.clone();
let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); drop(Rc::into_raw(a));
)* let f: &Fn($($var),*) -> R = &**my_handle.get();
f($($var),*).into_abi(&mut GlobalStack::new()) 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 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 unsafe impl<$($var,)* R> WasmClosure for FnMut($($var),*) -> R
where $($var: FromWasmAbi + 'static,)* where $($var: FromWasmAbi + 'static,)*
R: IntoWasmAbi + 'static, R: ReturnWasmAbi + 'static,
{ {
fn describe() { fn describe() {
<&mut Self>::describe(); <&mut Self>::describe();
@ -408,22 +351,28 @@ macro_rules! doit {
fn invoke_fn() -> u32 { fn invoke_fn() -> u32 {
#[allow(non_snake_case)] #[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>>, a: *const UnsafeCell<Box<FnMut($($var),*) -> R>>,
$($var: <$var as FromWasmAbi>::Abi),* $($var: <$var as FromWasmAbi>::Abi),*
) -> <R as IntoWasmAbi>::Abi { ) -> <R as ReturnWasmAbi>::Abi {
if a.is_null() { 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); // Make sure all stack variables are converted before we
let my_handle = a.clone(); // convert `ret` as it may throw (for `Result`, for
drop(Rc::into_raw(a)); // example)
let f: &mut FnMut($($var),*) -> R = &mut **my_handle.get(); let ret = {
let mut _stack = GlobalStack::new(); let a = Rc::from_raw(a);
$( let my_handle = a.clone();
let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); drop(Rc::into_raw(a));
)* let f: &mut FnMut($($var),*) -> R = &mut **my_handle.get();
f($($var),*).into_abi(&mut GlobalStack::new()) 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 invoke::<$($var,)* R> as u32
} }

View File

@ -1,34 +1,37 @@
#![allow(const_err)] // FIXME(rust-lang/rust#52603)
use core::mem; use core::mem;
use convert::{FromWasmAbi, IntoWasmAbi, GlobalStack, Stack}; use convert::{FromWasmAbi, IntoWasmAbi, GlobalStack, Stack, ReturnWasmAbi};
use throw; use throw_str;
macro_rules! stack_closures { macro_rules! stack_closures {
($( ($($var:ident)*) )*) => ($( ($( ($($var:ident)*) )*) => ($(
impl<'a, 'b, $($var,)* R> IntoWasmAbi for &'a (Fn($($var),*) -> R + 'b) impl<'a, 'b, $($var,)* R> IntoWasmAbi for &'a (Fn($($var),*) -> R + 'b)
where $($var: FromWasmAbi,)* where $($var: FromWasmAbi,)*
R: IntoWasmAbi R: ReturnWasmAbi
{ {
type Abi = u32; type Abi = u32;
fn into_abi(self, extra: &mut Stack) -> u32 { fn into_abi(self, extra: &mut Stack) -> u32 {
#[allow(non_snake_case)] #[allow(non_snake_case)]
unsafe extern fn invoke<$($var: FromWasmAbi,)* R: IntoWasmAbi>( unsafe extern fn invoke<$($var: FromWasmAbi,)* R: ReturnWasmAbi>(
a: usize, a: usize,
b: usize, b: usize,
$($var: <$var as FromWasmAbi>::Abi),* $($var: <$var as FromWasmAbi>::Abi),*
) -> <R as IntoWasmAbi>::Abi { ) -> <R as ReturnWasmAbi>::Abi {
if a == 0 { 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)); // Scope all local variables before we call `return_abi` to
let mut _stack = GlobalStack::new(); // ensure they're all destroyed as `return_abi` may throw
$( let ret = {
let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); let f: &Fn($($var),*) -> R = mem::transmute((a, b));
)* let mut _stack = GlobalStack::new();
f($($var),*).into_abi(&mut GlobalStack::new()) $(
let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack);
)*
f($($var),*)
};
ret.return_abi(&mut GlobalStack::new())
} }
unsafe { unsafe {
let (a, b): (usize, usize) = mem::transmute(self); 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) impl<'a, 'b, $($var,)* R> IntoWasmAbi for &'a mut (FnMut($($var),*) -> R + 'b)
where $($var: FromWasmAbi,)* where $($var: FromWasmAbi,)*
R: IntoWasmAbi R: ReturnWasmAbi
{ {
type Abi = u32; type Abi = u32;
fn into_abi(self, extra: &mut Stack) -> u32 { fn into_abi(self, extra: &mut Stack) -> u32 {
#[allow(non_snake_case)] #[allow(non_snake_case)]
unsafe extern fn invoke<$($var: FromWasmAbi,)* R: IntoWasmAbi>( unsafe extern fn invoke<$($var: FromWasmAbi,)* R: ReturnWasmAbi>(
a: usize, a: usize,
b: usize, b: usize,
$($var: <$var as FromWasmAbi>::Abi),* $($var: <$var as FromWasmAbi>::Abi),*
) -> <R as IntoWasmAbi>::Abi { ) -> <R as ReturnWasmAbi>::Abi {
if a == 0 { 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)); // Scope all local variables before we call `return_abi` to
let mut _stack = GlobalStack::new(); // ensure they're all destroyed as `return_abi` may throw
$( let ret = {
let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); let f: &mut FnMut($($var),*) -> R = mem::transmute((a, b));
)* let mut _stack = GlobalStack::new();
f($($var),*).into_abi(&mut GlobalStack::new()) $(
let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack);
)*
f($($var),*)
};
ret.return_abi(&mut GlobalStack::new())
} }
unsafe { unsafe {
let (a, b): (usize, usize) = mem::transmute(self); 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 core::mem::{self, ManuallyDrop};
use convert::{Stack, FromWasmAbi, IntoWasmAbi, RefFromWasmAbi}; use convert::{Stack, FromWasmAbi, IntoWasmAbi, RefFromWasmAbi};
use convert::{OptionIntoWasmAbi, OptionFromWasmAbi}; use convert::{OptionIntoWasmAbi, OptionFromWasmAbi, ReturnWasmAbi};
use convert::traits::WasmAbi; use convert::traits::WasmAbi;
use JsValue; use JsValue;
unsafe impl WasmAbi for () {}
#[repr(C)] #[repr(C)]
pub struct WasmOptionalI32 { pub struct WasmOptionalI32 {
pub present: u32, 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 i32 {}
unsafe impl WasmAbi for f32 {} unsafe impl WasmAbi for f32 {}
unsafe impl WasmAbi for f64 {} 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 RUST_STRUCT
CHAR CHAR
OPTIONAL OPTIONAL
UNIT
} }
#[inline(always)] // see `interpret.rs` in the the cli-support crate #[inline(always)] // see `interpret.rs` in the the cli-support crate
@ -152,22 +153,10 @@ macro_rules! doit {
inform(FUNCTION); inform(FUNCTION);
inform(cnt!($($var)*)); inform(cnt!($($var)*));
$(<$var as WasmDescribe>::describe();)* $(<$var as WasmDescribe>::describe();)*
inform(1);
<R as WasmDescribe>::describe(); <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 impl<'a, $($var,)* R> WasmDescribe for FnMut($($var),*) -> R + 'a
where $($var: WasmDescribe,)* where $($var: WasmDescribe,)*
R: WasmDescribe R: WasmDescribe
@ -176,21 +165,9 @@ macro_rules! doit {
inform(FUNCTION); inform(FUNCTION);
inform(cnt!($($var)*)); inform(cnt!($($var)*));
$(<$var as WasmDescribe>::describe();)* $(<$var as WasmDescribe>::describe();)*
inform(1);
<R as WasmDescribe>::describe(); <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(); 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::cell::UnsafeCell;
use core::fmt; use core::fmt;
use core::mem;
use core::ops::Deref; use core::ops::Deref;
use core::ptr; use core::ptr;
@ -428,6 +429,7 @@ externs! {
fn __wbindgen_is_string(idx: u32) -> u32; fn __wbindgen_is_string(idx: u32) -> u32;
fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8; fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8;
fn __wbindgen_throw(a: *const u8, b: usize) -> !; fn __wbindgen_throw(a: *const u8, b: usize) -> !;
fn __wbindgen_rethrow(a: u32) -> !;
fn __wbindgen_cb_drop(idx: u32) -> (); fn __wbindgen_cb_drop(idx: u32) -> ();
fn __wbindgen_cb_forget(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. /// Throws a JS exception.
/// ///
/// This function will throw a JS exception with the message provided. The /// 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 /// function will not return as the wasm stack will be popped when the exception
/// is thrown. /// 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] #[cold]
#[inline(never)] #[inline(never)]
pub fn throw(s: &str) -> ! { pub fn throw_str(s: &str) -> ! {
unsafe { unsafe {
__wbindgen_throw(s.as_ptr(), s.len()); __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` /// Returns a handle to this wasm instance's `WebAssembly.Memory`
pub fn memory() -> JsValue { pub fn memory() -> JsValue {
unsafe { unsafe {
@ -604,7 +640,7 @@ pub mod __rt {
#[cold] #[cold]
#[inline(never)] #[inline(never)]
fn throw_null() -> ! { 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. /// A vendored version of `RefCell` from the standard library.
@ -726,7 +762,7 @@ pub mod __rt {
} }
fn borrow_fail() -> ! { fn borrow_fail() -> ! {
super::throw( super::throw_str(
"recursive use of an object detected which would lead to \ "recursive use of an object detected which would lead to \
unsafe aliasing in rust", unsafe aliasing in rust",
); );
@ -748,7 +784,7 @@ pub mod __rt {
} }
} }
super::throw("invalid malloc request"); super::throw_str("invalid malloc request");
} }
#[no_mangle] #[no_mangle]

View File

@ -26,6 +26,7 @@ pub mod math;
pub mod node; pub mod node;
pub mod option; pub mod option;
pub mod optional_primitives; pub mod optional_primitives;
pub mod rethrow;
pub mod simple; pub mod simple;
pub mod slice; pub mod slice;
pub mod structural; 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)
}