diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 947ee455..c4425f9d 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, @@ -463,6 +464,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/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 25ff39f3..8017338c 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1733,7 +1733,6 @@ impl<'a> Context<'a> { } fn global(&mut self, s: &str) { - let s = s; let s = s.trim(); // Ensure a blank line between adjacent items, and ensure everything is @@ -1963,8 +1962,9 @@ impl<'a, 'b> SubContext<'a, 'b> { let js = Rust2Js::new(self.cx) .catch(import.catch) + .variadic(import.variadic) .process(descriptor.unwrap_function())? - .finish(&target); + .finish(&target)?; self.cx.export(&import.shim, &js, None); Ok(()) } diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index cb607f7c..a0fcd20a 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -1,4 +1,4 @@ -use failure::Error; +use failure::{self, Error}; use super::{Context, Js2Rust}; use descriptor::{Descriptor, Function}; @@ -36,6 +36,9 @@ pub struct Rust2Js<'a, 'b: 'a> { /// Whether or not we're catching JS exceptions catch: bool, + + /// Whether or not the last argument is a slice representing variadic arguments. + variadic: bool, } impl<'a, 'b> Rust2Js<'a, 'b> { @@ -50,6 +53,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { arg_idx: 0, ret_expr: String::new(), catch: false, + variadic: false, } } @@ -62,6 +66,15 @@ impl<'a, 'b> Rust2Js<'a, 'b> { self } + pub fn variadic(&mut self, variadic: bool) -> &mut Self { + if variadic { + self.cx.expose_uint32_memory(); + self.cx.expose_add_heap_object(); + } + self.variadic = variadic; + self + } + /// Generates all bindings necessary for the signature in `Function`, /// creating necessary argument conversions and return value processing. pub fn process(&mut self, function: &Function) -> Result<&mut Self, Error> { @@ -72,6 +85,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { Ok(self) } + /// Get a generated name for an argument. fn shim_argument(&mut self) -> String { let s = format!("arg{}", self.arg_idx); self.arg_idx += 1; @@ -515,7 +529,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { Ok(()) } - pub fn finish(&self, invoc: &str) -> String { + pub fn finish(&self, invoc: &str) -> Result { let mut ret = String::new(); ret.push_str("function("); ret.push_str(&self.shim_arguments.join(", ")); @@ -528,10 +542,24 @@ impl<'a, 'b> Rust2Js<'a, 'b> { ret.push_str(") {\n"); ret.push_str(&self.prelude); - let mut invoc = self.ret_expr.replace( - "JS", - &format!("{}({})", invoc, self.js_arguments.join(", ")), - ); + let mut invoc = if self.variadic { + if self.js_arguments.is_empty() { + return Err(failure::err_msg("a function with no arguments cannot be variadic")); + } + let last_arg = self.js_arguments.len() - 1; // check implies >= 0 + self.ret_expr.replace( + "JS", + &format!("{}({}, ...{})", + invoc, + self.js_arguments[..last_arg].join(", "), + self.js_arguments[last_arg]) + ) + } else { + self.ret_expr.replace( + "JS", + &format!("{}({})", invoc, self.js_arguments.join(", ")), + ) + }; if self.catch { let catch = "\ const view = getUint32Memory();\n\ @@ -566,7 +594,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { ret.push_str(&invoc); ret.push_str("\n}\n"); - return ret; + Ok(ret) } fn global_idx(&mut self) -> usize { diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 33ce3eb2..91f7f7ad 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, &field)?; 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,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> for syn::ForeignItemFn None, )?.0; let catch = opts.catch(); + let variadic = opts.variadic(); let js_ret = if catch { // TODO: this assumes a whole bunch: // @@ -533,6 +547,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()), @@ -545,6 +560,7 @@ impl ConvertToAst for syn::ForeignItemType { type Target = ast::ImportKind; fn convert(self, attrs: BindgenAttrs) -> Result { + assert_not_variadic(&attrs, &self)?; let js_name = attrs .js_name() .map_or_else(|| self.ident.to_string(), |s| s.to_string()); @@ -570,6 +586,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> for syn::ForeignItemSt if self.mutability.is_some() { bail_span!(self.mutability, "cannot import mutable globals yet") } + assert_not_variadic(&opts, &self)?; let default_name = self.ident.to_string(); let js_name = opts.js_name().unwrap_or(&default_name); let shim = format!( @@ -604,6 +621,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, &self)?; let default_name = self.ident.to_string(); let name = attrs.js_name().unwrap_or(&default_name); @@ -1074,6 +1092,15 @@ 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, span: &dyn ToTokens) -> Result<(), Diagnostic> { + if attrs.variadic() { + bail_span!(span, "the `variadic` attribute can only be applied to imported \ + (`extern`) functions") + } + Ok(()) +} + /// 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 86591e9d..b2250d6d 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -43,6 +43,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/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index 50e59dc6..b3463535 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -277,6 +277,7 @@ impl<'src> FirstPassRecord<'src> { }, rust_name: rust_ident(rust_name), js_ret: js_ret.clone(), + variadic: false, catch, structural, shim:{ diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 720e4b84..89dfa890 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -42,6 +42,7 @@ - [`module = "blah"`](./reference/attributes/on-js-imports/module.md) - [`static_method_of = Blah`](./reference/attributes/on-js-imports/static_method_of.md) - [`structural`](./reference/attributes/on-js-imports/structural.md) + - [variadic](./reference/attributes/on-js-imports/variadic.md) - [On Rust Exports](./reference/attributes/on-rust-exports/index.md) - [`constructor`](./reference/attributes/on-rust-exports/constructor.md) - [`js_name = Blah`](./reference/attributes/on-rust-exports/js_name.md) diff --git a/guide/src/reference/attributes/on-js-imports/variadic.md b/guide/src/reference/attributes/on-js-imports/variadic.md new file mode 100644 index 00000000..a9ad97b4 --- /dev/null +++ b/guide/src/reference/attributes/on-js-imports/variadic.md @@ -0,0 +1,38 @@ +# Variadic Parameters + +In javascript, both the types of function arguments, and the number of function arguments are +dynamic. For example + +```js +function sum(...rest) { + let i; + // the old way + let old_way = 0; + for (i=0; i i32; +} +``` + +when we call this function, the last argument will be expanded as the javascript expects. + diff --git a/guide/src/whirlwind-tour/what-else-can-we-do.md b/guide/src/whirlwind-tour/what-else-can-we-do.md index 80bc19b0..7d9ec2b0 100644 --- a/guide/src/whirlwind-tour/what-else-can-we-do.md +++ b/guide/src/whirlwind-tour/what-else-can-we-do.md @@ -64,6 +64,10 @@ extern { fn new() -> Awesome; #[wasm_bindgen(method)] fn get_internal(this: &Awesome) -> u32; + // We can call javascript functions that have a dynamic number of arguments, + // e.g. rust `sum(&[1, 2, 3])` will be called like `sum(1, 2, 3)` + #[wasm_bindgen(variadic)] + fn sum(vals: &[u32]) -> u32; } #[wasm_bindgen] @@ -143,5 +147,13 @@ export class Awesome { } } +export function sum(...args) { + let answer = 0; + for(var i=0; i u8; + #[wasm_bindgen(variadic)] + fn variadic_sum_u16(first: u16, second: u16, rest: &[u16]) -> u16; + #[wasm_bindgen(variadic)] + fn variadic_sum_u32(first: u32, second: u32, rest: &[u32]) -> u32; + #[wasm_bindgen(variadic)] + fn variadic_sum_u64(first: u64, second: u64, rest: &[u64]) -> u64; + //#[wasm_bindgen(variadic)] + //fn variadic_sum_usize(first: usize, second: usize, rest: &[usize]) -> usize; + #[wasm_bindgen(variadic)] + fn variadic_sum_i8(first: i8, second: i8, rest: &[i8]) -> i8; + #[wasm_bindgen(variadic)] + fn variadic_sum_i16(first: i16, second: i16, rest: &[i16]) -> i16; + #[wasm_bindgen(variadic)] + fn variadic_sum_i32(first: i32, second: i32, rest: &[i32]) -> i32; + #[wasm_bindgen(variadic)] + fn variadic_sum_i64(first: i64, second: i64, rest: &[i64]) -> i64; + //#[wasm_bindgen(variadic)] + //fn variadic_sum_isize(first: isize, second: isize, rest: &[isize]) -> isize; + #[wasm_bindgen(variadic)] + fn variadic_sum_f32(first: f32, second: f32, rest: &[f32]) -> f32; + #[wasm_bindgen(variadic)] + fn variadic_sum_f64(first: f64, second: f64, rest: &[f64]) -> f64; + //#[wasm_bindgen(variadic)] + //fn variadic_sum_opt(first: Option, second: Option, rest: &[Option]) -> u32; + //#[wasm_bindgen(variadic)] + //fn variadic_concat_str(first: &str, second: &str, rest: &[&str]) -> String; + //#[wasm_bindgen(variadic)] + //fn variadic_concat_string(first: String, + // second: String, + // rest: Vec) -> String; + #[wasm_bindgen(variadic)] + fn variadic_sum_rest_vec(first: u8, second: u8, rest: Vec) -> u8; + //#[wasm_bindgen(variadic)] + //fn variadic_compare_pairs(first: JsValue, second: JsValue, rest: &[JsValue]); + //TODO imported type +} + +// ints + +macro_rules! variadic_test_int { + ($fn_name:ident, $extern_name:ident) => { + #[wasm_bindgen_test] + fn $fn_name() { + assert_eq!($extern_name(1, 2, &[]), 3); + assert_eq!($extern_name(1, 2, &[3]), 6); + assert_eq!($extern_name(1, 2, &[3, 4]), 10); + } + } +} + +// The 64 tests throw js `Cannot mix BigInt and other types, use explicit conversions` +variadic_test_int!(u8, variadic_sum_u8); +variadic_test_int!(u16, variadic_sum_u16); +variadic_test_int!(u32, variadic_sum_u32); +//variadic_test_int!(u64, variadic_sum_u64); +//variadic_test_int!(usize, variadic_sum_usize); +variadic_test_int!(i8, variadic_sum_i8); +variadic_test_int!(i16, variadic_sum_i16); +variadic_test_int!(i32, variadic_sum_i32); +//variadic_test_int!(i64, variadic_sum_i64); +//variadic_test_int!(isize, variadic_sum_isize); + +// floats + +macro_rules! variadic_test_float { + ($fn_name:ident, $extern_name:ident) => { + #[wasm_bindgen_test] + fn $fn_name() { + assert_eq!($extern_name(1., 2., &[]), 3.); + assert_eq!($extern_name(1., 2., &[3.]), 6.); + assert_eq!($extern_name(1., 2., &[3., 4.]), 10.); + } + } +} + +variadic_test_float!(f32, variadic_sum_f32); +variadic_test_float!(f64, variadic_sum_f64); + +// strings + +// `the trait `wasm_bindgen::convert::IntoWasmAbi` is not implemented for `&[&str]` +/* +#[wasm_bindgen_test] +fn str() { + assert_eq!(variadic_concat_str("a ", "test", &[]), "a test"); + assert_eq!(variadic_concat_str("a", "nother ", &["test"]), "another test"); + assert_eq!(variadic_concat_str("yet ", "a", &["nother ", "test"]), "yet another test"); +} + +#[wasm_bindgen_test] +fn string() { + assert_eq!(variadic_concat_string("a ".into(), "test".into(), vec![]), "a test"); + assert_eq!(variadic_concat_string("a".into(), "nother ".into(), vec!["test".into()]), + "another test"); + assert_eq!(variadic_concat_string("yet ".into(), + "a".into(), + vec!["nother ".into(), "test".into()]), + "yet another test"); +} +*/ + +// options + +/* +#[wasm_bindgen_test] +fn opt() { + assert_eq!(variadic_sum_opt(Some(1), None, &[]), 1); + assert_eq!(variadic_sum_opt(Some(1), None, &[Some(2)]), 3); + assert_eq!(variadic_sum_opt(Some(1), None, &[None, Some(2)]), 3); +} +*/ + +// vec not slice + +#[wasm_bindgen_test] +fn rest_vec() { + assert_eq!(variadic_sum_rest_vec(1, 2, vec![]), 3); + assert_eq!(variadic_sum_rest_vec(1, 2, vec![3]), 6); + assert_eq!(variadic_sum_rest_vec(1, 2, vec![3, 4]), 10); +} + +// JsValue +//#[wasm_bindgen_test] +//fn jsvalue() { +// variadic_compare_pairs_jsvalue(true, true, vec![]); +// variadic_compare_pairs_jsvalue(false, false, vec![3, 3]); +//} +