From d9fd2147a03760574e30d630720096cfd0afab1e Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Sat, 18 Aug 2018 22:15:29 +0100 Subject: [PATCH] [wip] support variadic javascript function parameters --- crates/backend/src/ast.rs | 2 ++ crates/macro-support/src/parser.rs | 55 ++++++++++++++++++++++++++++++ crates/shared/src/lib.rs | 1 + tests/wasm/main.rs | 1 + tests/wasm/variadic.js | 6 ++++ tests/wasm/variadic.rs | 16 +++++++++ 6 files changed, 81 insertions(+) create mode 100644 tests/wasm/variadic.js create mode 100644 tests/wasm/variadic.rs diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index d8989e35..e229131a 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -85,6 +85,7 @@ pub struct ImportFunction { pub rust_name: Ident, pub js_ret: Option, pub catch: bool, + pub variadic: bool, pub structural: bool, pub kind: ImportFunctionKind, pub shim: Ident, @@ -449,6 +450,7 @@ impl ImportFunction { shared::ImportFunction { shim: self.shim.to_string(), catch: self.catch, + variadic: self.variadic, method, structural: self.structural, function: self.function.shared(), diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 18b198a6..824ee147 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -183,6 +183,14 @@ impl BindgenAttrs { _ => None, }) } + + /// Whether the variadic attributes is present + fn variadic(&self) -> bool { + self.attrs.iter().any(|a| match *a { + BindgenAttr::Variadic => true, + _ => false, + }) + } } impl syn::synom::Synom for BindgenAttrs { @@ -219,6 +227,7 @@ pub enum BindgenAttr { JsName(String), JsClass(String), Extends(Ident), + Variadic, } impl syn::synom::Synom for BindgenAttr { @@ -304,6 +313,8 @@ impl syn::synom::Synom for BindgenAttr { ns: call!(term2ident) >> (ns) )=> { BindgenAttr::Extends } + | + call!(term, "variadic") => { |_| BindgenAttr::Variadic } )); } @@ -365,6 +376,7 @@ impl<'a> ConvertToAst<()> for &'a mut syn::ItemStruct { let getter = shared::struct_field_get(&ident, &name_str); let setter = shared::struct_field_set(&ident, &name_str); let opts = BindgenAttrs::find(&mut field.attrs)?; + assert_not_variadic(&opts)?; let comments = extract_doc_comments(&field.attrs); fields.push(ast::StructField { name: name.clone(), @@ -395,6 +407,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> for syn::ForeignItemFn ) -> Result { let default_name = self.ident.to_string(); let js_name = opts.js_name().unwrap_or(&default_name); + let wasm = function_from_decl( js_name, self.decl.clone(), @@ -404,6 +417,10 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> for syn::ForeignItemFn None, )?.0; let catch = opts.catch(); + let variadic = opts.variadic(); + if variadic { + assert_last_param_is_slice(&self.decl)?; + } let js_ret = if catch { // TODO: this assumes a whole bunch: // @@ -532,6 +549,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> for syn::ForeignItemFn kind, js_ret, catch, + variadic, structural: opts.structural(), rust_name: self.ident.clone(), shim: Ident::new(&shim, Span::call_site()), @@ -544,6 +562,7 @@ impl ConvertToAst for syn::ForeignItemType { type Target = ast::ImportKind; fn convert(self, attrs: BindgenAttrs) -> Result { + assert_not_variadic(&attrs)?; let js_name = attrs .js_name() .map_or_else(|| self.ident.to_string(), |s| s.to_string()); @@ -567,6 +586,7 @@ impl ConvertToAst for syn::ForeignItemStatic { if self.mutability.is_some() { bail_span!(self.mutability, "cannot import mutable globals yet") } + assert_not_variadic(&opts)?; let default_name = self.ident.to_string(); let js_name = opts.js_name().unwrap_or(&default_name); let shim = format!( @@ -604,6 +624,7 @@ impl ConvertToAst for syn::ItemFn { if self.unsafety.is_some() { bail_span!(self.unsafety, "can only #[wasm_bindgen] safe functions"); } + assert_not_variadic(&attrs)?; let default_name = self.ident.to_string(); let name = attrs.js_name().unwrap_or(&default_name); @@ -1074,6 +1095,40 @@ fn assert_no_lifetimes(decl: &syn::FnDecl) -> Result<(), Diagnostic> { Diagnostic::from_vec(walk.diagnostics) } +/// This method always fails if the BindgenAttrs contain variadic +fn assert_not_variadic(attrs: &BindgenAttrs) -> Result<(), Diagnostic> { + match attrs.variadic() { + true => Err(Diagnostic::error("the `variadic` attribute can only be applied to imported \ + (`extern`) functions")), + false => Ok(()) + } +} + +/// Checks the function signature to ensure it finishes with a slice +/// +/// Example of a valid signature: `fn my_func(arg1: u64, res: &[u64])`. +fn assert_last_param_is_slice(decl: &syn::FnDecl) -> Result<(), Diagnostic> { + #[inline] + fn not_slice_error(tok: &dyn ToTokens) -> Diagnostic { + Diagnostic::span_error(tok, "for variadic extern functions, the last argument must be a \ + slice, to hold the arguments of unknown length") + } + + let arg = decl.inputs.last().ok_or_else(|| not_slice_error(&decl.inputs))?; + let ty = match arg.value() { + syn::FnArg::Captured(ref arg_cap) => &arg_cap.ty, + _ => return Err(not_slice_error(&arg)) + }; + match ty { + syn::Type::Reference(ref ref_ty) => match &*ref_ty.elem { + syn::Type::Slice(_) => Ok(()), + _ => Err(not_slice_error(ty)) + }, + _ => Err(not_slice_error(ty)) + } + +} + /// If the path is a single ident, return it. fn extract_path_ident(path: &syn::Path) -> Result { if path.leading_colon.is_some() { diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 20d78db7..af9205ef 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -41,6 +41,7 @@ pub enum ImportKind { pub struct ImportFunction { pub shim: String, pub catch: bool, + pub variadic: bool, pub method: Option, pub structural: bool, pub function: Function, diff --git a/tests/wasm/main.rs b/tests/wasm/main.rs index 6b783f93..07cc2c5d 100644 --- a/tests/wasm/main.rs +++ b/tests/wasm/main.rs @@ -31,3 +31,4 @@ pub mod slice; pub mod structural; pub mod u64; pub mod validate_prt; +pub mod variadic; diff --git a/tests/wasm/variadic.js b/tests/wasm/variadic.js new file mode 100644 index 00000000..71a722b6 --- /dev/null +++ b/tests/wasm/variadic.js @@ -0,0 +1,6 @@ +const wasm = require('wasm-bindgen-test.js'); +const assert = require('assert'); + +exports.variadic_sum = () => + Array.from(arguments).reduce((acc, val) => acc + val, 0); + diff --git a/tests/wasm/variadic.rs b/tests/wasm/variadic.rs new file mode 100644 index 00000000..5fc56e80 --- /dev/null +++ b/tests/wasm/variadic.rs @@ -0,0 +1,16 @@ +use wasm_bindgen_test::*; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(module = "tests/wasm/variadic.js")] +extern { + #[wasm_bindgen(variadic)] + fn variadic_sum(first: f64, second: f64, rest: &[f64]) -> f64; +} + +#[wasm_bindgen_test] +fn variadic_simple() { + assert_eq!(variadic_sum(1., 2., &[]), 3.); + assert_eq!(variadic_sum(1., 2., &[3.]), 6.); + assert_eq!(variadic_sum(1., 2., &[3., 4.]), 10.); +} +