diff --git a/crates/macros/src/function.rs b/crates/macros/src/function.rs index 5a389f1fe..670d32fed 100644 --- a/crates/macros/src/function.rs +++ b/crates/macros/src/function.rs @@ -27,6 +27,7 @@ pub struct Arg { pub nullable: bool, pub default: Option, pub as_ref: bool, + pub variadic: bool, } #[derive(Debug, Clone)] @@ -256,6 +257,7 @@ impl Arg { nullable: bool, default: Option, as_ref: bool, + variadic: bool, ) -> Self { Self { name, @@ -263,6 +265,7 @@ impl Arg { nullable, default, as_ref, + variadic, } } @@ -332,16 +335,21 @@ impl Arg { None => path.to_token_stream().to_string(), }, }; - Some(Arg::new( name, stringified, seg.ident == "Option" || default.is_some(), default, pass_by_ref, + false, )) } Type::Reference(ref_) => { + // If the variable is `&[&Zval]` treat it as the variadic argument. + let is_variadic = match ref_.elem.as_ref() { + Type::Slice(slice) => slice.elem.to_token_stream().to_string() == "& Zval", + _ => false, + }; // Returning references is invalid, so let's just create our arg Some(Arg::new( name, @@ -349,6 +357,7 @@ impl Arg { false, default, ref_.mutability.is_some(), + is_variadic, )) } _ => None, @@ -384,6 +393,8 @@ impl Arg { quote! { #name_ident.val().unwrap_or(#val.into()) } } else if self.nullable { quote! { #name_ident.val() } + } else if self.variadic { + quote! { &#name_ident.variadic_vals() } } else { quote! { match #name_ident.val() { @@ -405,10 +416,14 @@ impl Arg { /// the argument. pub fn get_arg_definition(&self) -> TokenStream { let name = &self.name; - let ty = self.get_type_ident(); + let mut ty = self.get_type_ident(); let null = self.nullable.then(|| quote! { .allow_null() }); let passed_by_ref = self.as_ref.then(|| quote! { .as_ref() }); + let is_variadic = self.variadic.then(|| quote! { .is_variadic() }); + if self.variadic { + ty = quote! { ::ext_php_rs::flags::DataType::Mixed } + } let default = self.default.as_ref().map(|val| { quote! { .default(#val) @@ -416,7 +431,7 @@ impl Arg { }); quote! { - ::ext_php_rs::args::Arg::new(#name, #ty) #null #passed_by_ref #default + ::ext_php_rs::args::Arg::new(#name, #ty) #null #passed_by_ref #default #is_variadic } } } diff --git a/crates/macros/src/module.rs b/crates/macros/src/module.rs index 2c118c056..f350fd472 100644 --- a/crates/macros/src/module.rs +++ b/crates/macros/src/module.rs @@ -202,6 +202,12 @@ impl Describe for Arg { fn describe(&self) -> TokenStream { let Arg { name, nullable, .. } = self; let ty: Type = syn::parse_str(&self.ty).expect("failed to parse previously parsed type"); + + let mut ty = + quote! { abi::Option::Some(<#ty as ::ext_php_rs::convert::FromZvalMut>::TYPE) }; + if self.variadic { + ty = quote! { abi::Option::Some(::ext_php_rs::flags::DataType::Array) } + } let default = if let Some(default) = &self.default { quote! { Some(#default.into()) } } else { @@ -211,7 +217,7 @@ impl Describe for Arg { quote! { Parameter { name: #name.into(), - ty: abi::Option::Some(<#ty as ::ext_php_rs::convert::FromZvalMut>::TYPE), + ty: #ty, nullable: #nullable, default: abi::Option::#default, } diff --git a/guide/src/macros/function.md b/guide/src/macros/function.md index 30fdb84d4..4538180f8 100644 --- a/guide/src/macros/function.md +++ b/guide/src/macros/function.md @@ -96,6 +96,26 @@ pub fn greet(name: String, age: Option, description: Option) -> Str # fn main() {} ``` +## Variadic Functions + +Variadic functions can be implemented by specifying the last argument in the Rust +function to the type `&[&Zval]`. This is the equivelant of a PHP function using +the `...$args` syntax. + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +# use ext_php_rs::prelude::*; +# use ext_php_rs::types::Zval; +/// This can be called from PHP as `add(1, 2, 3, 4, 5)` +#[php_function] +pub fn add(number: u32, numbers:&[&Zval]) -> u32 { + // numbers is a slice of 4 Zvals all of type long + number +} +# fn main() {} +``` + ## Returning `Result` You can also return a `Result` from the function. The error variant will be diff --git a/src/args.rs b/src/args.rs index 4bc928f1f..d21075b2b 100644 --- a/src/args.rs +++ b/src/args.rs @@ -27,6 +27,7 @@ pub struct Arg<'a> { variadic: bool, default_value: Option, zval: Option<&'a mut Zval>, + variadic_zvals: Vec>, } impl<'a> Arg<'a> { @@ -45,6 +46,7 @@ impl<'a> Arg<'a> { variadic: false, default_value: None, zval: None, + variadic_zvals: vec![], } } @@ -103,6 +105,18 @@ impl<'a> Arg<'a> { .and_then(|zv| T::from_zval_mut(zv.dereference_mut())) } + /// Retrice all the variadic values for this Rust argument. + pub fn variadic_vals(&'a mut self) -> Vec + where + T: FromZvalMut<'a>, + { + self.variadic_zvals + .iter_mut() + .filter_map(|zv| zv.as_mut()) + .filter_map(|zv| T::from_zval_mut(zv.dereference_mut())) + .collect() + } + /// Attempts to return a reference to the arguments internal Zval. /// /// # Returns @@ -226,8 +240,9 @@ impl<'a, 'b> ArgParser<'a, 'b> { let max_num_args = self.args.len(); let min_num_args = self.min_num_args.unwrap_or(max_num_args); let num_args = self.arg_zvals.len(); + let has_variadic = self.args.last().map_or(false, |arg| arg.variadic); - if num_args < min_num_args || num_args > max_num_args { + if num_args < min_num_args || (!has_variadic && num_args > max_num_args) { // SAFETY: Exported C function is safe, return value is unused and parameters // are copied. unsafe { zend_wrong_parameters_count_error(min_num_args as _, max_num_args as _) }; @@ -235,8 +250,17 @@ impl<'a, 'b> ArgParser<'a, 'b> { } for (i, arg_zval) in self.arg_zvals.into_iter().enumerate() { - if let Some(arg) = self.args.get_mut(i) { - arg.zval = arg_zval; + let arg = match self.args.get_mut(i) { + Some(arg) => Some(arg), + // Only select the last item if it's variadic + None => self.args.last_mut().filter(|arg| arg.variadic), + }; + if let Some(arg) = arg { + if arg.variadic { + arg.variadic_zvals.push(arg_zval); + } else { + arg.zval = arg_zval; + } } } diff --git a/src/builders/function.rs b/src/builders/function.rs index e75a90dec..60eddcc96 100644 --- a/src/builders/function.rs +++ b/src/builders/function.rs @@ -115,6 +115,11 @@ impl<'a> FunctionBuilder<'a> { self } + pub fn variadic(mut self) -> Self { + self.function.flags |= MethodFlags::Variadic.bits(); + self + } + /// Sets the return value of the function. /// /// # Parameters