mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-11-24 06:33:33 +03:00
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:
parent
ccced83b0e
commit
7cf4213283
@ -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! {
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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");
|
||||
|
@ -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),
|
||||
|
@ -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) => {
|
||||
|
@ -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)
|
||||
|
20
guide/src/reference/types/result.md
Normal file
20
guide/src/reference/types/result.md
Normal 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"!.
|
129
src/closure.rs
129
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<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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
)*)
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
44
src/lib.rs
44
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<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]
|
||||
|
@ -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
14
tests/wasm/rethrow.js
Normal 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
28
tests/wasm/rethrow.rs
Normal 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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user