Skip to content

Commit 0801e89

Browse files
Xenirayoramdelangen
andcommitted
fix(args): fix variadic args
Fixes: #337 Co-authored-by: Yoram <[email protected]>
1 parent 828b004 commit 0801e89

File tree

7 files changed

+85
-2
lines changed

7 files changed

+85
-2
lines changed

crates/macros/src/function.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,10 @@ impl TypedArg<'_> {
563563
quote! {
564564
#name.val().unwrap_or(#default.into())
565565
}
566+
} else if self.variadic {
567+
quote! {
568+
&#name.variadic_vals()
569+
}
566570
} else if self.nullable {
567571
// Originally I thought we could just use the below case for `null` options, as
568572
// `val()` will return `Option<Option<T>>`, however, this isn't the case when

src/args.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,12 @@ impl<'a, 'b> ArgParser<'a, 'b> {
250250
/// should break execution after seeing an error type.
251251
pub fn parse(mut self) -> Result<()> {
252252
let max_num_args = self.args.len();
253-
let min_num_args = self.min_num_args.unwrap_or(max_num_args);
253+
let mut min_num_args = self.min_num_args.unwrap_or(max_num_args);
254254
let num_args = self.arg_zvals.len();
255255
let has_variadic = self.args.last().is_some_and(|arg| arg.variadic);
256+
if has_variadic && min_num_args > 0 {
257+
min_num_args -= 1;
258+
}
256259

257260
if num_args < min_num_args || (!has_variadic && num_args > max_num_args) {
258261
// SAFETY: Exported C function is safe, return value is unused and parameters

src/builders/function.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,16 @@ impl<'a> FunctionBuilder<'a> {
164164
pub fn build(mut self) -> Result<FunctionEntry> {
165165
let mut args = Vec::with_capacity(self.args.len() + 1);
166166

167+
let n_req = self.n_req.unwrap_or(self.args.len());
168+
let n_req = if self.function.flags & MethodFlags::Variadic.bits() == 1 && n_req > 0 {
169+
n_req - 1
170+
} else {
171+
n_req
172+
};
173+
167174
// argument header, retval etc
168175
args.push(ArgInfo {
169-
name: self.n_req.unwrap_or(self.args.len()) as *const _,
176+
name: n_req as *const _,
170177
type_: match self.retval {
171178
Some(retval) => {
172179
ZendType::empty_from_type(retval, self.ret_as_ref, false, self.ret_as_null)

src/types/zval.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,3 +719,13 @@ impl<'a> FromZvalMut<'a> for &'a mut Zval {
719719
Some(zval)
720720
}
721721
}
722+
723+
impl<'a> FromZvalMut<'a> for &'a [&'a Zval] {
724+
const TYPE: DataType = DataType::Array;
725+
726+
fn from_zval_mut(_zval: &'a mut Zval) -> Option<Self> {
727+
unimplemented!(
728+
"This impl is currently purely to meet the trait bounds for variadic functions."
729+
)
730+
}
731+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
require "_utils.php";
4+
5+
$a = 'a';
6+
$b = 'b';
7+
$c = 'c';
8+
9+
$array = [$b, 'a','c'];
10+
11+
// Passing arguments as references
12+
$args = test_variadic_args();
13+
assert($args === [], 'Expected no arguments to be returned');
14+
15+
$args = test_variadic_args($a);
16+
assert($args === ['a'], 'Expected to return argument $a');
17+
18+
$args = test_variadic_args($a, $b, $c);
19+
assert($args === ['a', 'b', 'c'], 'Expected to return arguments $a, $b and $c');
20+
21+
$args = test_variadic_args(...$array);
22+
assert($args === ['b', 'a', 'c'], 'Expected to return an array with the array $array');
23+
24+
assert_exception_thrown('test_variadic_add_required');
25+
26+
// Values directly passed
27+
$sum = test_variadic_add_required(1, 2, 3); // 1
28+
assert($sum === 6, 'Expected to return 6');
29+
30+
$count = test_variadic_add_required(11); // 11
31+
assert($count === 11, 'Allow only one argument');
32+
33+
$types = test_variadic_args('a', 1, ['abc', 'def', 0.01], true, new stdClass);
34+
assert(gettype(end($types[2])) === 'double', 'Type of argument 2 and its last element should be a float of 0.01');
35+
assert($types[3], 'Arg 4 should be boolean true');
36+
assert($types[4] instanceof stdClass, 'Last argument is an instance of an StdClass');
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#[test]
2+
fn test_variadic_args() {
3+
assert!(crate::integration::run_php("variadic_args.php"));
4+
}

tests/src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,22 @@ fn key_to_zval(key: ArrayKey) -> Zval {
165165
}
166166
}
167167

168+
// Rust type &[&Zval] must be converted because to Vec<Zval> because of
169+
// lifetime hell.
170+
#[php_function]
171+
pub fn test_variadic_args(params: &[&Zval]) -> Vec<Zval> {
172+
params.iter().map(|x| x.shallow_clone()).collect()
173+
}
174+
175+
#[php_function]
176+
pub fn test_variadic_add_required(number: u32, numbers: &[&Zval]) -> u32 {
177+
number
178+
+ numbers
179+
.iter()
180+
.map(|x| x.long().unwrap() as u32)
181+
.sum::<u32>()
182+
}
183+
168184
#[php_class]
169185
pub struct TestClass {
170186
string: String,
@@ -233,6 +249,8 @@ pub fn build_module(module: ModuleBuilder) -> ModuleBuilder {
233249
.function(wrap_function!(iter_back))
234250
.function(wrap_function!(iter_next_back))
235251
.function(wrap_function!(test_class))
252+
.function(wrap_function!(test_variadic_args))
253+
.function(wrap_function!(test_variadic_add_required))
236254
}
237255

238256
#[cfg(test)]
@@ -304,4 +322,5 @@ mod integration {
304322
mod object;
305323
mod string;
306324
mod types;
325+
mod variadic_args;
307326
}

0 commit comments

Comments
 (0)