Merge pull request #792 from afdw/master

Add support for variadic arguments in WebIDL
This commit is contained in:
Alex Crichton 2018-09-11 16:05:56 -07:00 committed by GitHub
commit 9ca024a812
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 125 additions and 30 deletions

View File

@ -499,13 +499,26 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
return Err(failure::err_msg("a function with no arguments cannot be variadic")); return Err(failure::err_msg("a function with no arguments cannot be variadic"));
} }
let last_arg = self.js_arguments.len() - 1; // check implies >= 0 let last_arg = self.js_arguments.len() - 1; // check implies >= 0
self.ret_expr.replace( if self.js_arguments.len() != 1 {
"JS", self.ret_expr.replace(
&format!("{}({}, ...{})", "JS",
invoc, &format!(
self.js_arguments[..last_arg].join(", "), "{}({}, ...{})",
self.js_arguments[last_arg]) invoc,
) self.js_arguments[..last_arg].join(", "),
self.js_arguments[last_arg],
)
)
} else {
self.ret_expr.replace(
"JS",
&format!(
"{}(...{})",
invoc,
self.js_arguments[last_arg],
)
)
}
} else { } else {
self.ret_expr.replace( self.ret_expr.replace(
"JS", "JS",

View File

@ -110,6 +110,13 @@ global.OptionalAndUnionArguments = class OptionalAndUnionArguments {
} }
}; };
global.Variadic = class Variadic {
constructor() {}
sum(...values) {
return values.reduce((a, b) => a + b, 0);
}
};
global.PartialInterface = class PartialInterface { global.PartialInterface = class PartialInterface {
get un() { get un() {
return 1; return 1;

View File

@ -94,6 +94,24 @@ fn optional_and_union_arguments() {
assert_eq!(f.m_with_b_and_str_and_opt_bool("abc", false, "5", Some(true)), "string, abc, boolean, false, string, 5, boolean, true"); assert_eq!(f.m_with_b_and_str_and_opt_bool("abc", false, "5", Some(true)), "string, abc, boolean, false, string, 5, boolean, true");
} }
#[wasm_bindgen_test]
fn variadic() {
let f = Variadic::new().unwrap();
assert_eq!(f.sum_5(1, 2, 3, 4, 5), 15);
assert_eq!(
f.sum(
&::js_sys::Array::of5(
&JsValue::from_f64(1f64),
&JsValue::from_f64(2f64),
&JsValue::from_f64(3f64),
&JsValue::from_f64(4f64),
&JsValue::from_f64(5f64),
)
),
15
);
}
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn unforgeable_is_structural() { fn unforgeable_is_structural() {
let f = Unforgeable::new().unwrap(); let f = Unforgeable::new().unwrap();

View File

@ -57,6 +57,11 @@ interface OptionalAndUnionArguments {
); );
}; };
[Constructor()]
interface Variadic {
short sum(short... values);
};
[Constructor()] [Constructor()]
interface Unforgeable { interface Unforgeable {
[Unforgeable] readonly attribute short uno; [Unforgeable] readonly attribute short uno;

View File

@ -95,18 +95,19 @@ pub(crate) struct OperationData<'src> {
pub(crate) is_static: bool, pub(crate) is_static: bool,
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub(crate) struct Signature<'src> { pub(crate) struct Signature<'src> {
pub(crate) args: Vec<Arg<'src>>, pub(crate) args: Vec<Arg<'src>>,
pub(crate) ret: weedle::types::ReturnType<'src>, pub(crate) ret: weedle::types::ReturnType<'src>,
pub(crate) attrs: &'src Option<ExtendedAttributeList<'src>>, pub(crate) attrs: &'src Option<ExtendedAttributeList<'src>>,
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub(crate) struct Arg<'src> { pub(crate) struct Arg<'src> {
pub(crate) name: &'src str, pub(crate) name: &'src str,
pub(crate) ty: &'src weedle::types::Type<'src>, pub(crate) ty: &'src weedle::types::Type<'src>,
pub(crate) optional: bool, pub(crate) optional: bool,
pub(crate) variadic: bool,
} }
/// Implemented on an AST node to populate the `FirstPassRecord` struct. /// Implemented on an AST node to populate the `FirstPassRecord` struct.
@ -258,19 +259,17 @@ fn first_pass_operation<'src>(
}; };
let mut args = Vec::with_capacity(arguments.len()); let mut args = Vec::with_capacity(arguments.len());
for argument in arguments { for argument in arguments {
let arg = match argument { let (name, ty, optional, variadic) = match argument {
Argument::Single(single) => single, Argument::Single(single) =>
Argument::Variadic(v) => { (single.identifier.0, &single.type_.type_, single.optional.is_some(), false),
warn!("Unsupported variadic argument {} in {}", Argument::Variadic(variadic) =>
v.identifier.0, (variadic.identifier.0, &variadic.type_, false, true),
self_name);
return
}
}; };
args.push(Arg { args.push(Arg {
name: arg.identifier.0, name,
ty: &arg.type_.type_, ty,
optional: arg.optional.is_some(), optional,
variadic,
}); });
} }
for id in ids { for id in ids {

View File

@ -142,7 +142,7 @@ fn builtin_idents() -> BTreeSet<Ident> {
vec![ vec![
"str", "char", "bool", "JsValue", "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "str", "char", "bool", "JsValue", "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64",
"usize", "isize", "f32", "f64", "Result", "String", "Vec", "Option", "usize", "isize", "f32", "f64", "Result", "String", "Vec", "Option",
"ArrayBuffer", "Object", "Promise", "Function", "Array", "ArrayBuffer", "Object", "Promise", "Function",
].into_iter() ].into_iter()
.map(|id| proc_macro2::Ident::new(id, proc_macro2::Span::call_site())), .map(|id| proc_macro2::Ident::new(id, proc_macro2::Span::call_site())),
) )

View File

@ -13,6 +13,12 @@ use weedle::literal::{ConstValue, FloatLit, IntegerLit};
use first_pass::{FirstPassRecord, OperationId, OperationData, Signature}; use first_pass::{FirstPassRecord, OperationId, OperationData, Signature};
use idl_type::{IdlType, ToIdlType}; use idl_type::{IdlType, ToIdlType};
/// For variadic operations an overload with a `js_sys::Array` argument is generated alongside with
/// `operation_name_0`, `operation_name_1`, `operation_name_2`, ..., `operation_name_n` overloads
/// which have the count of arguments for passing values to the variadic argument
/// in their names, where `n` is this constant.
const MAX_VARIADIC_ARGUMENTS_COUNT: usize = 7;
/// Take a type and create an immutable shared reference to that type. /// Take a type and create an immutable shared reference to that type.
pub(crate) fn shared_ref(ty: syn::Type, mutable: bool) -> syn::Type { pub(crate) fn shared_ref(ty: syn::Type, mutable: bool) -> syn::Type {
syn::TypeReference { syn::TypeReference {
@ -223,6 +229,7 @@ impl<'src> FirstPassRecord<'src> {
kind: backend::ast::ImportFunctionKind, kind: backend::ast::ImportFunctionKind,
structural: bool, structural: bool,
catch: bool, catch: bool,
variadic: bool,
doc_comment: Option<String>, doc_comment: Option<String>,
) -> Option<backend::ast::ImportFunction> where 'src: 'a { ) -> Option<backend::ast::ImportFunction> where 'src: 'a {
// Convert all of the arguments from their IDL type to a `syn` type, // Convert all of the arguments from their IDL type to a `syn` type,
@ -245,7 +252,9 @@ impl<'src> FirstPassRecord<'src> {
} else { } else {
Vec::with_capacity(idl_arguments.size_hint().0) Vec::with_capacity(idl_arguments.size_hint().0)
}; };
for (argument_name, idl_type) in idl_arguments { let idl_arguments: Vec<_> = idl_arguments.collect();
let arguments_count = idl_arguments.len();
for (i, (argument_name, idl_type)) in idl_arguments.into_iter().enumerate() {
let syn_type = match idl_type.to_syn_type(TypePosition::Argument) { let syn_type = match idl_type.to_syn_type(TypePosition::Argument) {
Some(t) => t, Some(t) => t,
None => { None => {
@ -257,6 +266,12 @@ impl<'src> FirstPassRecord<'src> {
return None return None
} }
}; };
let syn_type = if variadic && i == arguments_count - 1 {
let path = vec![rust_ident("js_sys"), rust_ident("Array")];
shared_ref(leading_colon_path_ty(path), false)
} else {
syn_type
};
let argument_name = rust_ident(&argument_name.to_snake_case()); let argument_name = rust_ident(&argument_name.to_snake_case());
arguments.push(simple_fn_arg(argument_name, syn_type)); arguments.push(simple_fn_arg(argument_name, syn_type));
} }
@ -296,10 +311,10 @@ impl<'src> FirstPassRecord<'src> {
}, },
rust_name: rust_ident(rust_name), rust_name: rust_ident(rust_name),
js_ret: js_ret.clone(), js_ret: js_ret.clone(),
variadic: false, variadic,
catch, catch,
structural, structural,
shim:{ shim: {
let ns = match kind { let ns = match kind {
backend::ast::ImportFunctionKind::ScopedMethod { .. } | backend::ast::ImportFunctionKind::ScopedMethod { .. } |
backend::ast::ImportFunctionKind::Normal => "", backend::ast::ImportFunctionKind::Normal => "",
@ -334,7 +349,8 @@ impl<'src> FirstPassRecord<'src> {
kind, kind,
is_structural(attrs.as_ref(), container_attrs), is_structural(attrs.as_ref(), container_attrs),
throws(attrs), throws(attrs),
Some(format!("The `{}` getter\n\n{}", name, mdn_doc(self_name, Some(name)))) false,
Some(format!("The `{}` getter\n\n{}", name, mdn_doc(self_name, Some(name)))),
) )
} }
@ -360,7 +376,8 @@ impl<'src> FirstPassRecord<'src> {
kind, kind,
is_structural(attrs.as_ref(), container_attrs), is_structural(attrs.as_ref(), container_attrs),
throws(attrs), throws(attrs),
Some(format!("The `{}` setter\n\n{}", name, mdn_doc(self_name, Some(name)))) false,
Some(format!("The `{}` setter\n\n{}", name, mdn_doc(self_name, Some(name)))),
) )
} }
@ -413,7 +430,14 @@ impl<'src> FirstPassRecord<'src> {
let mut idl_args = Vec::with_capacity(signature.args.len()); let mut idl_args = Vec::with_capacity(signature.args.len());
for (i, arg) in signature.args.iter().enumerate() { for (i, arg) in signature.args.iter().enumerate() {
if arg.optional { if arg.optional {
assert!(signature.args[i..].iter().all(|a| a.optional)); assert!(
signature
.args[i..]
.iter()
.all(|arg| arg.optional || arg.variadic),
"Not optional or variadic argument after optional argument: {:?}",
signature.args,
);
signatures.push((signature, idl_args.clone())); signatures.push((signature, idl_args.clone()));
} }
match arg.ty.to_idl_type(self) { match arg.ty.to_idl_type(self) {
@ -559,18 +583,47 @@ impl<'src> FirstPassRecord<'src> {
rust_name.push_str(&snake_case_ident(arg_name)); rust_name.push_str(&snake_case_ident(arg_name));
} }
} }
let structural = force_structural || is_structural(signature.orig.attrs.as_ref(), container_attrs);
let catch = force_throws || throws(&signature.orig.attrs);
let variadic = signature.args.len() == signature.orig.args.len()
&& signature.orig.args.last().map(|arg| arg.variadic).unwrap_or(false);
ret.extend(self.create_one_function( ret.extend(self.create_one_function(
name, name,
&rust_name, &rust_name,
signature.args.iter() signature.args.iter()
.zip(&signature.orig.args) .zip(&signature.orig.args)
.map(|(ty, orig_arg)| (orig_arg.name, ty)), .map(|(idl_type, orig_arg)| (orig_arg.name, idl_type)),
&ret_ty, &ret_ty,
kind.clone(), kind.clone(),
force_structural || is_structural(signature.orig.attrs.as_ref(), container_attrs), structural,
force_throws || throws(&signature.orig.attrs), catch,
variadic,
None, None,
)); ));
if !variadic {
continue;
}
let last_idl_type = &signature.args[signature.args.len() - 1];
let last_name = signature.orig.args[signature.args.len() - 1].name;
for i in 0..=MAX_VARIADIC_ARGUMENTS_COUNT {
ret.extend(self.create_one_function(
name,
&format!("{}_{}", rust_name, i),
signature.args[..signature.args.len() - 1].iter()
.zip(&signature.orig.args)
.map(|(idl_type, orig_arg)| (orig_arg.name.to_string(), idl_type))
.chain((1..=i).map(|j| (format!("{}_{}", last_name, j), last_idl_type)))
.collect::<Vec<_>>()
.iter()
.map(|(name, idl_type)| (&name[..], idl_type.clone())),
&ret_ty,
kind.clone(),
structural,
catch,
false,
None,
));
}
} }
return ret; return ret;
} }