Merge pull request #726 from derekdreery/variadic_js_functions

Support variadic javascript function parameters
This commit is contained in:
Alex Crichton 2018-09-03 10:28:58 -07:00 committed by GitHub
commit 1a00e94324
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 314 additions and 9 deletions

View File

@ -85,6 +85,7 @@ pub struct ImportFunction {
pub rust_name: Ident,
pub js_ret: Option<syn::Type>,
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(),

View File

@ -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(())
}

View File

@ -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<String, Error> {
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 {

View File

@ -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<String>)> for syn::ForeignItemFn
) -> Result<Self::Target, Diagnostic> {
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<String>)> 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<String>)> 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<BindgenAttrs> for syn::ForeignItemType {
type Target = ast::ImportKind;
fn convert(self, attrs: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
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<String>)> 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<BindgenAttrs> 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<Ident, Diagnostic> {
if path.leading_colon.is_some() {

View File

@ -43,6 +43,7 @@ pub enum ImportKind {
pub struct ImportFunction {
pub shim: String,
pub catch: bool,
pub variadic: bool,
pub method: Option<MethodData>,
pub structural: bool,
pub function: Function,

View File

@ -277,6 +277,7 @@ impl<'src> FirstPassRecord<'src> {
},
rust_name: rust_ident(rust_name),
js_ret: js_ret.clone(),
variadic: false,
catch,
structural,
shim:{

View File

@ -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)

View File

@ -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<arguments.length; i++) {
old_way += arguments[i];
}
// the new way
let new_way = 0;
for (i=0; i<rest.length; i++) {
new_way += rest[i];
}
// both give the same answer
assert(old_way === new_way);
return new_way;
}
```
This function doesn't translate directly into rust, since we don't currently support variadic
arguments on the wasm target. To bind to it, we use a slice as the last argument, and annotate the
function as variadic:
```rust
#[wasm_bindgen]
extern {
#[wasm_bindgen(variadic)]
fn sum(args: &[i32]) -> i32;
}
```
when we call this function, the last argument will be expanded as the javascript expects.

View File

@ -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<args.length; i++) {
answer += args[i];
}
return answer;
}
booted.then(main);
```

View File

@ -31,3 +31,4 @@ pub mod slice;
pub mod structural;
pub mod u64;
pub mod validate_prt;
pub mod variadic;

59
tests/wasm/variadic.js Normal file
View File

@ -0,0 +1,59 @@
const assert = require('assert');
// a function for testing numbers
function variadic_sum(...args) {
let answer = 0;
for(var i=0; i<args.length; i++) {
answer += args[i];
}
return answer;
}
exports.variadic_sum_u8 = variadic_sum;
exports.variadic_sum_u16 = variadic_sum;
exports.variadic_sum_u32 = variadic_sum;
exports.variadic_sum_u64 = variadic_sum;
exports.variadic_sum_i8 = variadic_sum;
exports.variadic_sum_i16 = variadic_sum;
exports.variadic_sum_i32 = variadic_sum;
exports.variadic_sum_i64 = variadic_sum;
exports.variadic_sum_f32 = variadic_sum;
exports.variadic_sum_f64 = variadic_sum;
exports.variadic_sum_rest_vec = variadic_sum;
// a function for testing nullable numbers
function variadic_sum_opt(...args) {
let answer = 0;
for(var i=0; i<args.length; i++) {
if(args[i] != null) {
answer += args[i];
}
}
return answer;
}
exports.variadic_sum_opt = variadic_sum_opt;
// a function for testing strings
function variadic_concat(...args) {
let answer = "";
for(var i=0; i<args.length; i++) {
answer = `${answer}${args[i]}`;
}
return answer;
}
exports.variadic_concat_str = variadic_concat;
exports.variadic_concat_string = variadic_concat;
// a test that takes any type of arguments (for testing JsValue).
function variadic_compare_pairs(...args) {
assert(args.length % 2 == 0, "args must be sequence of pairs");
for(var i=0; i<args.length; i++) {
const first = args[i++];
const second = args[i];
assert.equal(first, second);
}
}
exports.variadic_compare_pairs = variadic_compare_pairs;

135
tests/wasm/variadic.rs Normal file
View File

@ -0,0 +1,135 @@
use wasm_bindgen_test::*;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(module = "tests/wasm/variadic.js")]
extern {
#[wasm_bindgen(variadic)]
fn variadic_sum_u8(first: u8, second: u8, rest: &[u8]) -> 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<u32>, second: Option<u32>, rest: &[Option<u32>]) -> 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>) -> String;
#[wasm_bindgen(variadic)]
fn variadic_sum_rest_vec(first: u8, second: u8, rest: Vec<u8>) -> 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 <int>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]);
//}