diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index a0fcd20a..ccf29385 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -547,13 +547,26 @@ impl<'a, 'b> Rust2Js<'a, 'b> { 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]) - ) + if self.js_arguments.len() != 1 { + 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[last_arg], + ) + ) + } } else { self.ret_expr.replace( "JS", diff --git a/crates/webidl-tests/simple.js b/crates/webidl-tests/simple.js index d6859032..5a56a1a1 100644 --- a/crates/webidl-tests/simple.js +++ b/crates/webidl-tests/simple.js @@ -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 { get un() { return 1; diff --git a/crates/webidl-tests/simple.rs b/crates/webidl-tests/simple.rs index e5e4d230..1dce1b08 100644 --- a/crates/webidl-tests/simple.rs +++ b/crates/webidl-tests/simple.rs @@ -90,6 +90,12 @@ 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"); } +#[wasm_bindgen_test] +fn variadic() { + let f = Variadic::new().unwrap(); + assert_eq!(f.sum(Box::new([1, 2, 3, 4, 5])), 15); +} + #[wasm_bindgen_test] fn unforgeable_is_structural() { let f = Unforgeable::new().unwrap(); diff --git a/crates/webidl-tests/simple.webidl b/crates/webidl-tests/simple.webidl index 63d7b9d2..99153d96 100644 --- a/crates/webidl-tests/simple.webidl +++ b/crates/webidl-tests/simple.webidl @@ -57,6 +57,11 @@ interface OptionalAndUnionArguments { ); }; +[Constructor()] +interface Variadic { + short sum(short... values); +}; + [Constructor()] interface Unforgeable { [Unforgeable] readonly attribute short uno; diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 3762ad14..c5e091f1 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -89,18 +89,19 @@ pub(crate) struct OperationData<'src> { pub(crate) is_static: bool, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub(crate) struct Signature<'src> { pub(crate) args: Vec>, pub(crate) ret: weedle::types::ReturnType<'src>, pub(crate) attrs: &'src Option>, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub(crate) struct Arg<'src> { pub(crate) name: &'src str, pub(crate) ty: &'src weedle::types::Type<'src>, pub(crate) optional: bool, + pub(crate) variadic: bool, } /// Implemented on an AST node to populate the `FirstPassRecord` struct. @@ -259,19 +260,17 @@ fn first_pass_operation<'src>( }; let mut args = Vec::with_capacity(arguments.len()); for argument in arguments { - let arg = match argument { - Argument::Single(single) => single, - Argument::Variadic(v) => { - warn!("Unsupported variadic argument {} in {}", - v.identifier.0, - self_name); - return - } + let (name, ty, optional, variadic) = match argument { + Argument::Single(single) => + (single.identifier.0, &single.type_.type_, single.optional.is_some(), false), + Argument::Variadic(variadic) => + (variadic.identifier.0, &variadic.type_, false, true), }; args.push(Arg { - name: arg.identifier.0, - ty: &arg.type_.type_, - optional: arg.optional.is_some(), + name, + ty, + optional, + variadic, }); } for id in ids { diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index c8253644..4ba10d21 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -137,7 +137,7 @@ fn builtin_idents() -> BTreeSet { BTreeSet::from_iter( vec![ "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", "Box", "Vec", "Option", "ArrayBuffer", "Object", "Promise", ].into_iter() .map(|id| proc_macro2::Ident::new(id, proc_macro2::Span::call_site())), diff --git a/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index 513989ea..aebe2cb2 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -172,6 +172,24 @@ pub(crate) fn slice_ty(t: syn::Type) -> syn::Type { }.into() } +/// From `T` create `Box`. +pub(crate) fn box_ty(t: syn::Type) -> syn::Type { + let arguments = syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { + colon2_token: None, + lt_token: Default::default(), + args: FromIterator::from_iter(vec![ + syn::GenericArgument::Type(t), + ]), + gt_token: Default::default(), + }); + + let ident = raw_ident("Box"); + let seg = syn::PathSegment { ident, arguments }; + let path: syn::Path = seg.into(); + let ty = syn::TypePath { qself: None, path }; + ty.into() +} + /// From `T` create `Vec`. pub(crate) fn vec_ty(t: syn::Type) -> syn::Type { let arguments = syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { @@ -223,6 +241,7 @@ impl<'src> FirstPassRecord<'src> { kind: backend::ast::ImportFunctionKind, structural: bool, catch: bool, + variadic: bool, doc_comment: Option, ) -> Option where 'src: 'a { // Convert all of the arguments from their IDL type to a `syn` type, @@ -245,7 +264,9 @@ impl<'src> FirstPassRecord<'src> { } else { 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) { Some(t) => t, None => { @@ -257,6 +278,20 @@ impl<'src> FirstPassRecord<'src> { return None } }; + let syn_type = if variadic && i == arguments_count - 1 { + // Blacklist unsupported slice types + match idl_type { + | IdlType::DomString + | IdlType::ByteString + | IdlType::UsvString => return None, + + IdlType::Interface(..) => return None, + + _ => box_ty(slice_ty(syn_type)) + } + } else { + syn_type + }; let argument_name = rust_ident(&argument_name.to_snake_case()); arguments.push(simple_fn_arg(argument_name, syn_type)); } @@ -296,10 +331,10 @@ impl<'src> FirstPassRecord<'src> { }, rust_name: rust_ident(rust_name), js_ret: js_ret.clone(), - variadic: false, + variadic, catch, structural, - shim:{ + shim: { let ns = match kind { backend::ast::ImportFunctionKind::ScopedMethod { .. } | backend::ast::ImportFunctionKind::Normal => "", @@ -334,7 +369,8 @@ impl<'src> FirstPassRecord<'src> { kind, is_structural(attrs.as_ref(), container_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 +396,8 @@ impl<'src> FirstPassRecord<'src> { kind, is_structural(attrs.as_ref(), container_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 +450,14 @@ impl<'src> FirstPassRecord<'src> { let mut idl_args = Vec::with_capacity(signature.args.len()); for (i, arg) in signature.args.iter().enumerate() { 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())); } match arg.ty.to_idl_type(self) { @@ -569,6 +613,8 @@ impl<'src> FirstPassRecord<'src> { kind.clone(), force_structural || is_structural(signature.orig.attrs.as_ref(), container_attrs), force_throws || throws(&signature.orig.attrs), + signature.args.len() == signature.orig.args.len() + && signature.orig.args.last().map(|arg| arg.variadic).unwrap_or(false), None, )); }