Skip to content

Commit a002e98

Browse files
authored
Merge pull request #300 from davidcole1340/variadic-support
Support for variadic functions
2 parents 4fad486 + 82f9aef commit a002e98

File tree

5 files changed

+77
-7
lines changed

5 files changed

+77
-7
lines changed

crates/macros/src/function.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub struct Arg {
2727
pub nullable: bool,
2828
pub default: Option<String>,
2929
pub as_ref: bool,
30+
pub variadic: bool,
3031
}
3132

3233
#[derive(Debug, Clone)]
@@ -256,13 +257,15 @@ impl Arg {
256257
nullable: bool,
257258
default: Option<String>,
258259
as_ref: bool,
260+
variadic: bool,
259261
) -> Self {
260262
Self {
261263
name,
262264
ty,
263265
nullable,
264266
default,
265267
as_ref,
268+
variadic,
266269
}
267270
}
268271

@@ -332,23 +335,29 @@ impl Arg {
332335
None => path.to_token_stream().to_string(),
333336
},
334337
};
335-
336338
Some(Arg::new(
337339
name,
338340
stringified,
339341
seg.ident == "Option" || default.is_some(),
340342
default,
341343
pass_by_ref,
344+
false,
342345
))
343346
}
344347
Type::Reference(ref_) => {
348+
// If the variable is `&[&Zval]` treat it as the variadic argument.
349+
let is_variadic = match ref_.elem.as_ref() {
350+
Type::Slice(slice) => slice.elem.to_token_stream().to_string() == "& Zval",
351+
_ => false,
352+
};
345353
// Returning references is invalid, so let's just create our arg
346354
Some(Arg::new(
347355
name,
348356
ref_.to_token_stream().to_string(),
349357
false,
350358
default,
351359
ref_.mutability.is_some(),
360+
is_variadic,
352361
))
353362
}
354363
_ => None,
@@ -384,6 +393,8 @@ impl Arg {
384393
quote! { #name_ident.val().unwrap_or(#val.into()) }
385394
} else if self.nullable {
386395
quote! { #name_ident.val() }
396+
} else if self.variadic {
397+
quote! { &#name_ident.variadic_vals() }
387398
} else {
388399
quote! {
389400
match #name_ident.val() {
@@ -405,18 +416,22 @@ impl Arg {
405416
/// the argument.
406417
pub fn get_arg_definition(&self) -> TokenStream {
407418
let name = &self.name;
408-
let ty = self.get_type_ident();
419+
let mut ty = self.get_type_ident();
409420

410421
let null = self.nullable.then(|| quote! { .allow_null() });
411422
let passed_by_ref = self.as_ref.then(|| quote! { .as_ref() });
423+
let is_variadic = self.variadic.then(|| quote! { .is_variadic() });
424+
if self.variadic {
425+
ty = quote! { ::ext_php_rs::flags::DataType::Mixed }
426+
}
412427
let default = self.default.as_ref().map(|val| {
413428
quote! {
414429
.default(#val)
415430
}
416431
});
417432

418433
quote! {
419-
::ext_php_rs::args::Arg::new(#name, #ty) #null #passed_by_ref #default
434+
::ext_php_rs::args::Arg::new(#name, #ty) #null #passed_by_ref #default #is_variadic
420435
}
421436
}
422437
}

crates/macros/src/module.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,12 @@ impl Describe for Arg {
202202
fn describe(&self) -> TokenStream {
203203
let Arg { name, nullable, .. } = self;
204204
let ty: Type = syn::parse_str(&self.ty).expect("failed to parse previously parsed type");
205+
206+
let mut ty =
207+
quote! { abi::Option::Some(<#ty as ::ext_php_rs::convert::FromZvalMut>::TYPE) };
208+
if self.variadic {
209+
ty = quote! { abi::Option::Some(::ext_php_rs::flags::DataType::Array) }
210+
}
205211
let default = if let Some(default) = &self.default {
206212
quote! { Some(#default.into()) }
207213
} else {
@@ -211,7 +217,7 @@ impl Describe for Arg {
211217
quote! {
212218
Parameter {
213219
name: #name.into(),
214-
ty: abi::Option::Some(<#ty as ::ext_php_rs::convert::FromZvalMut>::TYPE),
220+
ty: #ty,
215221
nullable: #nullable,
216222
default: abi::Option::#default,
217223
}

guide/src/macros/function.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,26 @@ pub fn greet(name: String, age: Option<i32>, description: Option<String>) -> Str
9696
# fn main() {}
9797
```
9898

99+
## Variadic Functions
100+
101+
Variadic functions can be implemented by specifying the last argument in the Rust
102+
function to the type `&[&Zval]`. This is the equivelant of a PHP function using
103+
the `...$args` syntax.
104+
105+
```rust,no_run
106+
# #![cfg_attr(windows, feature(abi_vectorcall))]
107+
# extern crate ext_php_rs;
108+
# use ext_php_rs::prelude::*;
109+
# use ext_php_rs::types::Zval;
110+
/// This can be called from PHP as `add(1, 2, 3, 4, 5)`
111+
#[php_function]
112+
pub fn add(number: u32, numbers:&[&Zval]) -> u32 {
113+
// numbers is a slice of 4 Zvals all of type long
114+
number
115+
}
116+
# fn main() {}
117+
```
118+
99119
## Returning `Result<T, E>`
100120

101121
You can also return a `Result` from the function. The error variant will be

src/args.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub struct Arg<'a> {
2727
variadic: bool,
2828
default_value: Option<String>,
2929
zval: Option<&'a mut Zval>,
30+
variadic_zvals: Vec<Option<&'a mut Zval>>,
3031
}
3132

3233
impl<'a> Arg<'a> {
@@ -45,6 +46,7 @@ impl<'a> Arg<'a> {
4546
variadic: false,
4647
default_value: None,
4748
zval: None,
49+
variadic_zvals: vec![],
4850
}
4951
}
5052

@@ -103,6 +105,18 @@ impl<'a> Arg<'a> {
103105
.and_then(|zv| T::from_zval_mut(zv.dereference_mut()))
104106
}
105107

108+
/// Retrice all the variadic values for this Rust argument.
109+
pub fn variadic_vals<T>(&'a mut self) -> Vec<T>
110+
where
111+
T: FromZvalMut<'a>,
112+
{
113+
self.variadic_zvals
114+
.iter_mut()
115+
.filter_map(|zv| zv.as_mut())
116+
.filter_map(|zv| T::from_zval_mut(zv.dereference_mut()))
117+
.collect()
118+
}
119+
106120
/// Attempts to return a reference to the arguments internal Zval.
107121
///
108122
/// # Returns
@@ -226,17 +240,27 @@ impl<'a, 'b> ArgParser<'a, 'b> {
226240
let max_num_args = self.args.len();
227241
let min_num_args = self.min_num_args.unwrap_or(max_num_args);
228242
let num_args = self.arg_zvals.len();
243+
let has_variadic = self.args.last().map_or(false, |arg| arg.variadic);
229244

230-
if num_args < min_num_args || num_args > max_num_args {
245+
if num_args < min_num_args || (!has_variadic && num_args > max_num_args) {
231246
// SAFETY: Exported C function is safe, return value is unused and parameters
232247
// are copied.
233248
unsafe { zend_wrong_parameters_count_error(min_num_args as _, max_num_args as _) };
234249
return Err(Error::IncorrectArguments(num_args, min_num_args));
235250
}
236251

237252
for (i, arg_zval) in self.arg_zvals.into_iter().enumerate() {
238-
if let Some(arg) = self.args.get_mut(i) {
239-
arg.zval = arg_zval;
253+
let arg = match self.args.get_mut(i) {
254+
Some(arg) => Some(arg),
255+
// Only select the last item if it's variadic
256+
None => self.args.last_mut().filter(|arg| arg.variadic),
257+
};
258+
if let Some(arg) = arg {
259+
if arg.variadic {
260+
arg.variadic_zvals.push(arg_zval);
261+
} else {
262+
arg.zval = arg_zval;
263+
}
240264
}
241265
}
242266

src/builders/function.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ impl<'a> FunctionBuilder<'a> {
115115
self
116116
}
117117

118+
pub fn variadic(mut self) -> Self {
119+
self.function.flags |= MethodFlags::Variadic.bits();
120+
self
121+
}
122+
118123
/// Sets the return value of the function.
119124
///
120125
/// # Parameters

0 commit comments

Comments
 (0)