Skip to content

Commit

Permalink
Merge pull request #300 from davidcole1340/variadic-support
Browse files Browse the repository at this point in the history
Support for variadic functions
  • Loading branch information
danog authored Dec 1, 2023
2 parents 4fad486 + 82f9aef commit a002e98
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 7 deletions.
21 changes: 18 additions & 3 deletions crates/macros/src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub struct Arg {
pub nullable: bool,
pub default: Option<String>,
pub as_ref: bool,
pub variadic: bool,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -256,13 +257,15 @@ impl Arg {
nullable: bool,
default: Option<String>,
as_ref: bool,
variadic: bool,
) -> Self {
Self {
name,
ty,
nullable,
default,
as_ref,
variadic,
}
}

Expand Down Expand Up @@ -332,23 +335,29 @@ 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,
ref_.to_token_stream().to_string(),
false,
default,
ref_.mutability.is_some(),
is_variadic,
))
}
_ => None,
Expand Down Expand Up @@ -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() {
Expand All @@ -405,18 +416,22 @@ 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)
}
});

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
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion crates/macros/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
}
Expand Down
20 changes: 20 additions & 0 deletions guide/src/macros/function.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,26 @@ pub fn greet(name: String, age: Option<i32>, description: Option<String>) -> 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<T, E>`

You can also return a `Result` from the function. The error variant will be
Expand Down
30 changes: 27 additions & 3 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub struct Arg<'a> {
variadic: bool,
default_value: Option<String>,
zval: Option<&'a mut Zval>,
variadic_zvals: Vec<Option<&'a mut Zval>>,
}

impl<'a> Arg<'a> {
Expand All @@ -45,6 +46,7 @@ impl<'a> Arg<'a> {
variadic: false,
default_value: None,
zval: None,
variadic_zvals: vec![],
}
}

Expand Down Expand Up @@ -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<T>(&'a mut self) -> Vec<T>
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
Expand Down Expand Up @@ -226,17 +240,27 @@ 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 _) };
return Err(Error::IncorrectArguments(num_args, min_num_args));
}

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;
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/builders/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit a002e98

Please sign in to comment.