Skip to content

Commit 1a00e94

Browse files
authored
Merge pull request #726 from derekdreery/variadic_js_functions
Support variadic javascript function parameters
2 parents e4cad4b + 5c7e638 commit 1a00e94

File tree

12 files changed

+314
-9
lines changed

12 files changed

+314
-9
lines changed

crates/backend/src/ast.rs

+2
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ pub struct ImportFunction {
8585
pub rust_name: Ident,
8686
pub js_ret: Option<syn::Type>,
8787
pub catch: bool,
88+
pub variadic: bool,
8889
pub structural: bool,
8990
pub kind: ImportFunctionKind,
9091
pub shim: Ident,
@@ -463,6 +464,7 @@ impl ImportFunction {
463464
shared::ImportFunction {
464465
shim: self.shim.to_string(),
465466
catch: self.catch,
467+
variadic: self.variadic,
466468
method,
467469
structural: self.structural,
468470
function: self.function.shared(),

crates/cli-support/src/js/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1733,7 +1733,6 @@ impl<'a> Context<'a> {
17331733
}
17341734

17351735
fn global(&mut self, s: &str) {
1736-
let s = s;
17371736
let s = s.trim();
17381737

17391738
// Ensure a blank line between adjacent items, and ensure everything is
@@ -1963,8 +1962,9 @@ impl<'a, 'b> SubContext<'a, 'b> {
19631962

19641963
let js = Rust2Js::new(self.cx)
19651964
.catch(import.catch)
1965+
.variadic(import.variadic)
19661966
.process(descriptor.unwrap_function())?
1967-
.finish(&target);
1967+
.finish(&target)?;
19681968
self.cx.export(&import.shim, &js, None);
19691969
Ok(())
19701970
}

crates/cli-support/src/js/rust2js.rs

+35-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use failure::Error;
1+
use failure::{self, Error};
22

33
use super::{Context, Js2Rust};
44
use descriptor::{Descriptor, Function};
@@ -36,6 +36,9 @@ pub struct Rust2Js<'a, 'b: 'a> {
3636

3737
/// Whether or not we're catching JS exceptions
3838
catch: bool,
39+
40+
/// Whether or not the last argument is a slice representing variadic arguments.
41+
variadic: bool,
3942
}
4043

4144
impl<'a, 'b> Rust2Js<'a, 'b> {
@@ -50,6 +53,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
5053
arg_idx: 0,
5154
ret_expr: String::new(),
5255
catch: false,
56+
variadic: false,
5357
}
5458
}
5559

@@ -62,6 +66,15 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
6266
self
6367
}
6468

69+
pub fn variadic(&mut self, variadic: bool) -> &mut Self {
70+
if variadic {
71+
self.cx.expose_uint32_memory();
72+
self.cx.expose_add_heap_object();
73+
}
74+
self.variadic = variadic;
75+
self
76+
}
77+
6578
/// Generates all bindings necessary for the signature in `Function`,
6679
/// creating necessary argument conversions and return value processing.
6780
pub fn process(&mut self, function: &Function) -> Result<&mut Self, Error> {
@@ -72,6 +85,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
7285
Ok(self)
7386
}
7487

88+
/// Get a generated name for an argument.
7589
fn shim_argument(&mut self) -> String {
7690
let s = format!("arg{}", self.arg_idx);
7791
self.arg_idx += 1;
@@ -515,7 +529,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
515529
Ok(())
516530
}
517531

518-
pub fn finish(&self, invoc: &str) -> String {
532+
pub fn finish(&self, invoc: &str) -> Result<String, Error> {
519533
let mut ret = String::new();
520534
ret.push_str("function(");
521535
ret.push_str(&self.shim_arguments.join(", "));
@@ -528,10 +542,24 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
528542
ret.push_str(") {\n");
529543
ret.push_str(&self.prelude);
530544

531-
let mut invoc = self.ret_expr.replace(
532-
"JS",
533-
&format!("{}({})", invoc, self.js_arguments.join(", ")),
534-
);
545+
let mut invoc = if self.variadic {
546+
if self.js_arguments.is_empty() {
547+
return Err(failure::err_msg("a function with no arguments cannot be variadic"));
548+
}
549+
let last_arg = self.js_arguments.len() - 1; // check implies >= 0
550+
self.ret_expr.replace(
551+
"JS",
552+
&format!("{}({}, ...{})",
553+
invoc,
554+
self.js_arguments[..last_arg].join(", "),
555+
self.js_arguments[last_arg])
556+
)
557+
} else {
558+
self.ret_expr.replace(
559+
"JS",
560+
&format!("{}({})", invoc, self.js_arguments.join(", ")),
561+
)
562+
};
535563
if self.catch {
536564
let catch = "\
537565
const view = getUint32Memory();\n\
@@ -566,7 +594,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
566594
ret.push_str(&invoc);
567595

568596
ret.push_str("\n}\n");
569-
return ret;
597+
Ok(ret)
570598
}
571599

572600
fn global_idx(&mut self) -> usize {

crates/macro-support/src/parser.rs

+27
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,14 @@ impl BindgenAttrs {
183183
_ => None,
184184
})
185185
}
186+
187+
/// Whether the variadic attributes is present
188+
fn variadic(&self) -> bool {
189+
self.attrs.iter().any(|a| match *a {
190+
BindgenAttr::Variadic => true,
191+
_ => false,
192+
})
193+
}
186194
}
187195

188196
impl syn::synom::Synom for BindgenAttrs {
@@ -219,6 +227,7 @@ pub enum BindgenAttr {
219227
JsName(String),
220228
JsClass(String),
221229
Extends(Ident),
230+
Variadic,
222231
}
223232

224233
impl syn::synom::Synom for BindgenAttr {
@@ -304,6 +313,8 @@ impl syn::synom::Synom for BindgenAttr {
304313
ns: call!(term2ident) >>
305314
(ns)
306315
)=> { BindgenAttr::Extends }
316+
|
317+
call!(term, "variadic") => { |_| BindgenAttr::Variadic }
307318
));
308319
}
309320

@@ -365,6 +376,7 @@ impl<'a> ConvertToAst<()> for &'a mut syn::ItemStruct {
365376
let getter = shared::struct_field_get(&ident, &name_str);
366377
let setter = shared::struct_field_set(&ident, &name_str);
367378
let opts = BindgenAttrs::find(&mut field.attrs)?;
379+
assert_not_variadic(&opts, &field)?;
368380
let comments = extract_doc_comments(&field.attrs);
369381
fields.push(ast::StructField {
370382
name: name.clone(),
@@ -395,6 +407,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
395407
) -> Result<Self::Target, Diagnostic> {
396408
let default_name = self.ident.to_string();
397409
let js_name = opts.js_name().unwrap_or(&default_name);
410+
398411
let wasm = function_from_decl(
399412
js_name,
400413
self.decl.clone(),
@@ -404,6 +417,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
404417
None,
405418
)?.0;
406419
let catch = opts.catch();
420+
let variadic = opts.variadic();
407421
let js_ret = if catch {
408422
// TODO: this assumes a whole bunch:
409423
//
@@ -533,6 +547,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
533547
kind,
534548
js_ret,
535549
catch,
550+
variadic,
536551
structural: opts.structural(),
537552
rust_name: self.ident.clone(),
538553
shim: Ident::new(&shim, Span::call_site()),
@@ -545,6 +560,7 @@ impl ConvertToAst<BindgenAttrs> for syn::ForeignItemType {
545560
type Target = ast::ImportKind;
546561

547562
fn convert(self, attrs: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
563+
assert_not_variadic(&attrs, &self)?;
548564
let js_name = attrs
549565
.js_name()
550566
.map_or_else(|| self.ident.to_string(), |s| s.to_string());
@@ -570,6 +586,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemSt
570586
if self.mutability.is_some() {
571587
bail_span!(self.mutability, "cannot import mutable globals yet")
572588
}
589+
assert_not_variadic(&opts, &self)?;
573590
let default_name = self.ident.to_string();
574591
let js_name = opts.js_name().unwrap_or(&default_name);
575592
let shim = format!(
@@ -604,6 +621,7 @@ impl ConvertToAst<BindgenAttrs> for syn::ItemFn {
604621
if self.unsafety.is_some() {
605622
bail_span!(self.unsafety, "can only #[wasm_bindgen] safe functions");
606623
}
624+
assert_not_variadic(&attrs, &self)?;
607625

608626
let default_name = self.ident.to_string();
609627
let name = attrs.js_name().unwrap_or(&default_name);
@@ -1074,6 +1092,15 @@ fn assert_no_lifetimes(decl: &syn::FnDecl) -> Result<(), Diagnostic> {
10741092
Diagnostic::from_vec(walk.diagnostics)
10751093
}
10761094

1095+
/// This method always fails if the BindgenAttrs contain variadic
1096+
fn assert_not_variadic(attrs: &BindgenAttrs, span: &dyn ToTokens) -> Result<(), Diagnostic> {
1097+
if attrs.variadic() {
1098+
bail_span!(span, "the `variadic` attribute can only be applied to imported \
1099+
(`extern`) functions")
1100+
}
1101+
Ok(())
1102+
}
1103+
10771104
/// If the path is a single ident, return it.
10781105
fn extract_path_ident(path: &syn::Path) -> Result<Ident, Diagnostic> {
10791106
if path.leading_colon.is_some() {

crates/shared/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub enum ImportKind {
4343
pub struct ImportFunction {
4444
pub shim: String,
4545
pub catch: bool,
46+
pub variadic: bool,
4647
pub method: Option<MethodData>,
4748
pub structural: bool,
4849
pub function: Function,

crates/webidl/src/util.rs

+1
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ impl<'src> FirstPassRecord<'src> {
277277
},
278278
rust_name: rust_ident(rust_name),
279279
js_ret: js_ret.clone(),
280+
variadic: false,
280281
catch,
281282
structural,
282283
shim:{

guide/src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
- [`module = "blah"`](./reference/attributes/on-js-imports/module.md)
4343
- [`static_method_of = Blah`](./reference/attributes/on-js-imports/static_method_of.md)
4444
- [`structural`](./reference/attributes/on-js-imports/structural.md)
45+
- [variadic](./reference/attributes/on-js-imports/variadic.md)
4546
- [On Rust Exports](./reference/attributes/on-rust-exports/index.md)
4647
- [`constructor`](./reference/attributes/on-rust-exports/constructor.md)
4748
- [`js_name = Blah`](./reference/attributes/on-rust-exports/js_name.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Variadic Parameters
2+
3+
In javascript, both the types of function arguments, and the number of function arguments are
4+
dynamic. For example
5+
6+
```js
7+
function sum(...rest) {
8+
let i;
9+
// the old way
10+
let old_way = 0;
11+
for (i=0; i<arguments.length; i++) {
12+
old_way += arguments[i];
13+
}
14+
// the new way
15+
let new_way = 0;
16+
for (i=0; i<rest.length; i++) {
17+
new_way += rest[i];
18+
}
19+
// both give the same answer
20+
assert(old_way === new_way);
21+
return new_way;
22+
}
23+
```
24+
25+
This function doesn't translate directly into rust, since we don't currently support variadic
26+
arguments on the wasm target. To bind to it, we use a slice as the last argument, and annotate the
27+
function as variadic:
28+
29+
```rust
30+
#[wasm_bindgen]
31+
extern {
32+
#[wasm_bindgen(variadic)]
33+
fn sum(args: &[i32]) -> i32;
34+
}
35+
```
36+
37+
when we call this function, the last argument will be expanded as the javascript expects.
38+

guide/src/whirlwind-tour/what-else-can-we-do.md

+12
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ extern {
6464
fn new() -> Awesome;
6565
#[wasm_bindgen(method)]
6666
fn get_internal(this: &Awesome) -> u32;
67+
// We can call javascript functions that have a dynamic number of arguments,
68+
// e.g. rust `sum(&[1, 2, 3])` will be called like `sum(1, 2, 3)`
69+
#[wasm_bindgen(variadic)]
70+
fn sum(vals: &[u32]) -> u32;
6771
}
6872
6973
#[wasm_bindgen]
@@ -143,5 +147,13 @@ export class Awesome {
143147
}
144148
}
145149

150+
export function sum(...args) {
151+
let answer = 0;
152+
for(var i=0; i<args.length; i++) {
153+
answer += args[i];
154+
}
155+
return answer;
156+
}
157+
146158
booted.then(main);
147159
```

tests/wasm/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ pub mod slice;
3131
pub mod structural;
3232
pub mod u64;
3333
pub mod validate_prt;
34+
pub mod variadic;

tests/wasm/variadic.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const assert = require('assert');
2+
3+
// a function for testing numbers
4+
function variadic_sum(...args) {
5+
let answer = 0;
6+
for(var i=0; i<args.length; i++) {
7+
answer += args[i];
8+
}
9+
return answer;
10+
}
11+
12+
exports.variadic_sum_u8 = variadic_sum;
13+
exports.variadic_sum_u16 = variadic_sum;
14+
exports.variadic_sum_u32 = variadic_sum;
15+
exports.variadic_sum_u64 = variadic_sum;
16+
exports.variadic_sum_i8 = variadic_sum;
17+
exports.variadic_sum_i16 = variadic_sum;
18+
exports.variadic_sum_i32 = variadic_sum;
19+
exports.variadic_sum_i64 = variadic_sum;
20+
exports.variadic_sum_f32 = variadic_sum;
21+
exports.variadic_sum_f64 = variadic_sum;
22+
exports.variadic_sum_rest_vec = variadic_sum;
23+
24+
// a function for testing nullable numbers
25+
function variadic_sum_opt(...args) {
26+
let answer = 0;
27+
for(var i=0; i<args.length; i++) {
28+
if(args[i] != null) {
29+
answer += args[i];
30+
}
31+
}
32+
return answer;
33+
}
34+
35+
exports.variadic_sum_opt = variadic_sum_opt;
36+
37+
// a function for testing strings
38+
function variadic_concat(...args) {
39+
let answer = "";
40+
for(var i=0; i<args.length; i++) {
41+
answer = `${answer}${args[i]}`;
42+
}
43+
return answer;
44+
}
45+
46+
exports.variadic_concat_str = variadic_concat;
47+
exports.variadic_concat_string = variadic_concat;
48+
49+
// a test that takes any type of arguments (for testing JsValue).
50+
function variadic_compare_pairs(...args) {
51+
assert(args.length % 2 == 0, "args must be sequence of pairs");
52+
for(var i=0; i<args.length; i++) {
53+
const first = args[i++];
54+
const second = args[i];
55+
assert.equal(first, second);
56+
}
57+
}
58+
59+
exports.variadic_compare_pairs = variadic_compare_pairs;

0 commit comments

Comments
 (0)