Implement optionally catching exceptions

This commit is contained in:
Alex Crichton
2018-02-06 19:04:12 -08:00
parent 43ee52bcbf
commit e9d612a343
7 changed files with 313 additions and 71 deletions

View File

@ -22,6 +22,7 @@ pub struct Import {
}
pub struct ImportFunction {
pub catch: bool,
pub ident: syn::Ident,
pub wasm_function: Function,
pub rust_decl: Box<syn::FnDecl>,
@ -32,7 +33,12 @@ pub struct ImportFunction {
pub struct ImportStruct {
pub module: Option<String>,
pub name: syn::Ident,
pub functions: Vec<(ImportFunctionKind, ImportFunction)>,
pub functions: Vec<ImportStructFunction>,
}
pub struct ImportStructFunction {
pub kind: ImportFunctionKind,
pub function: ImportFunction,
}
pub enum ImportFunctionKind {
@ -138,7 +144,7 @@ impl Program {
_ => panic!("only foreign functions allowed for now, not statics"),
};
let (wasm, mutable) = Function::from_decl(f.ident, &f.decl, allow_self);
let (mut wasm, mutable) = Function::from_decl(f.ident, &f.decl, allow_self);
let is_method = match mutable {
Some(false) => true,
None => false,
@ -146,6 +152,19 @@ impl Program {
panic!("mutable self methods not allowed in extern structs");
}
};
let opts = BindgenOpts::from(&f.attrs);
if opts.catch {
// TODO: this assumes a whole bunch:
//
// * The outer type is actually a `Result`
// * The error type is a `JsValue`
// * The actual type is the first type parameter
//
// should probably fix this one day...
wasm.ret = extract_first_ty_param(wasm.ret.as_ref())
.expect("can't `catch` without returning a Result");
}
(ImportFunction {
rust_attrs: f.attrs.clone(),
@ -153,6 +172,7 @@ impl Program {
rust_decl: f.decl.clone(),
ident: f.ident.clone(),
wasm_function: wasm,
catch: opts.catch,
}, is_method)
}
@ -163,40 +183,14 @@ impl Program {
let kind = if method {
ImportFunctionKind::Method
} else {
let new = f.rust_attrs.iter()
.filter_map(|a| a.interpret_meta())
.filter_map(|m| {
match m {
syn::Meta::List(i) => {
if i.ident == "wasm_bindgen" {
Some(i.nested)
} else {
None
}
}
_ => None,
}
})
.flat_map(|a| a)
.filter_map(|a| {
match a {
syn::NestedMeta::Meta(a) => Some(a),
_ => None,
}
})
.any(|a| {
match a {
syn::Meta::Word(a) => a == "constructor",
_ => false,
}
});
if new {
let opts = BindgenOpts::from(&f.rust_attrs);
if opts.constructor {
ImportFunctionKind::JsConstructor
} else {
ImportFunctionKind::Static
}
};
(kind, f)
ImportStructFunction { kind, function: f }
})
.collect();
self.imported_structs.push(ImportStruct {
@ -473,8 +467,8 @@ impl ImportStruct {
("name", &|a| a.str(self.name.as_ref())),
("functions", &|a| {
a.list(&self.functions,
|&(ref kind, ref f), a| {
let (method, new) = match *kind {
|f, a| {
let (method, new) = match f.kind {
ImportFunctionKind::Method => (true, false),
ImportFunctionKind::JsConstructor => (false, true),
ImportFunctionKind::Static => (false, false),
@ -482,7 +476,8 @@ impl ImportStruct {
a.fields(&[
("method", &|a| a.bool(method)),
("js_new", &|a| a.bool(new)),
("function", &|a| f.wasm_function.wbg_literal(a)),
("catch", &|a| a.bool(f.function.catch)),
("function", &|a| f.function.wasm_function.wbg_literal(a)),
]);
})
}),
@ -494,6 +489,7 @@ impl Import {
fn wbg_literal(&self, a: &mut LiteralBuilder) {
a.fields(&[
("module", &|a| a.str(&self.module)),
("catch", &|a| a.bool(self.function.catch)),
("function", &|a| self.function.wasm_function.wbg_literal(a)),
]);
}
@ -636,3 +632,78 @@ impl<'a> LiteralBuilder<'a> {
self.append("]");
}
}
#[derive(Default)]
struct BindgenOpts {
catch: bool,
constructor: bool,
}
impl BindgenOpts {
fn from(attrs: &[syn::Attribute]) -> BindgenOpts {
let mut opts = BindgenOpts::default();
let attrs = attrs.iter()
.filter_map(|a| a.interpret_meta())
.filter_map(|m| {
match m {
syn::Meta::List(i) => {
if i.ident == "wasm_bindgen" {
Some(i.nested)
} else {
None
}
}
_ => None,
}
})
.flat_map(|a| a)
.filter_map(|a| {
match a {
syn::NestedMeta::Meta(a) => Some(a),
_ => None,
}
});
for attr in attrs {
match attr {
syn::Meta::Word(a) => {
if a == "constructor" {
opts.constructor = true;
} else if a == "catch" {
opts.catch = true;
}
}
_ => {}
}
}
return opts
}
}
fn extract_first_ty_param(ty: Option<&Type>) -> Option<Option<Type>> {
let ty = match ty {
Some(t) => t,
None => return Some(None)
};
let ty = match *ty {
Type::ByValue(ref t) => t,
_ => return None,
};
let path = match *ty {
syn::Type::Path(syn::TypePath { qself: None, ref path }) => path,
_ => return None,
};
let seg = path.segments.last()?.into_value();
let generics = match seg.arguments {
syn::PathArguments::AngleBracketed(ref t) => t,
_ => return None,
};
let ty = match *generics.args.first()?.into_value() {
syn::GenericArgument::Type(ref t) => t,
_ => return None,
};
match *ty {
syn::Type::Tuple(ref t) if t.elems.len() == 0 => return Some(None),
_ => {}
}
Some(Some(Type::from(ty)))
}

View File

@ -366,12 +366,12 @@ fn bindgen_imported_struct(import: &ast::ImportStruct, tokens: &mut Tokens) {
let mut methods = Tokens::new();
for &(_, ref f) in import.functions.iter() {
for f in import.functions.iter() {
let import_name = shared::mangled_import_name(
Some(&import.name.to_string()),
f.wasm_function.name.as_ref(),
f.function.wasm_function.name.as_ref(),
);
bindgen_import_function(f, &import_name, &mut methods);
bindgen_import_function(&f.function, &import_name, &mut methods);
}
(my_quote! {
@ -501,7 +501,7 @@ fn bindgen_import_function(import: &ast::ImportFunction,
}
}
let abi_ret;
let convert_ret;
let mut convert_ret;
match import.wasm_function.ret {
Some(ast::Type::ByValue(ref t)) => {
abi_ret = my_quote! {
@ -534,10 +534,29 @@ fn bindgen_import_function(import: &ast::ImportFunction,
Some(ast::Type::ByMutRef(_)) => panic!("can't return a borrowed ref"),
None => {
abi_ret = my_quote! { () };
convert_ret = my_quote! {};
convert_ret = my_quote! { () };
}
}
let mut exceptional_ret = my_quote! {};
if import.catch {
let exn_data = syn::Ident::from("exn_data");
let exn_data_ptr = syn::Ident::from("exn_data_ptr");
abi_argument_names.push(exn_data_ptr);
abi_arguments.push(my_quote! { #exn_data_ptr: *mut u32 });
arg_conversions.push(my_quote! {
let mut #exn_data = [0; 2];
let mut #exn_data_ptr = #exn_data.as_mut_ptr();
});
convert_ret = my_quote! { Ok(#convert_ret) };
exceptional_ret = my_quote! {
if #exn_data[0] == 1 {
return Err(<::wasm_bindgen::JsValue as
::wasm_bindgen::convert::WasmBoundary>::from_js(#exn_data[1]))
}
};
}
let name = import.ident;
let import_name = syn::Ident::from(import_name);
(quote! {
@ -548,6 +567,7 @@ fn bindgen_import_function(import: &ast::ImportFunction,
unsafe {
#(#arg_conversions)*
let #ret_ident = #import_name(#(#abi_argument_names),*);
#exceptional_ret
#convert_ret
}
}