Skip to content

Commit 1b5a214

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

File tree

6 files changed

+96
-16
lines changed

6 files changed

+96
-16
lines changed

crates/macros/src/function.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,6 @@ impl<'a> Function<'a> {
158158
.iter()
159159
.map(TypedArg::arg_builder)
160160
.collect::<Result<Vec<_>>>()?;
161-
let variadic = self.args.typed.iter().any(|arg| arg.variadic).then(|| {
162-
quote! {
163-
.variadic()
164-
}
165-
});
166161
let returns = self.output.as_ref().map(|output| {
167162
quote! {
168163
.returns(
@@ -219,7 +214,6 @@ impl<'a> Function<'a> {
219214
#(.arg(&mut #required_arg_names))*
220215
.not_required()
221216
#(.arg(&mut #not_required_arg_names))*
222-
#variadic
223217
.parse();
224218
if parse_result.is_err() {
225219
return;
@@ -264,7 +258,6 @@ impl<'a> Function<'a> {
264258
#(.arg(#required_args))*
265259
.not_required()
266260
#(.arg(#not_required_args))*
267-
#variadic
268261
#returns
269262
#docs
270263
})
@@ -432,6 +425,7 @@ impl<'a> Args<'a> {
432425
let as_ref = ref_.mutability.is_some();
433426
match ref_.elem.as_ref() {
434427
Type::Slice(slice) => (
428+
// TODO: Allow specifying the variadic type.
435429
slice.elem.to_token_stream().to_string() == "& Zval",
436430
as_ref,
437431
ty.clone(),
@@ -512,6 +506,21 @@ impl TypedArg<'_> {
512506
fn clean_ty(&self) -> Type {
513507
let mut ty = self.ty.clone();
514508
ty.drop_lifetimes();
509+
510+
// Variadic arguments are passed as slices, so we need to extract the
511+
// inner type.
512+
if self.variadic {
513+
let reference = if let Type::Reference(r) = &ty {
514+
r
515+
} else {
516+
return ty;
517+
};
518+
519+
if let Type::Slice(inner) = &*reference.elem {
520+
return *inner.elem.clone();
521+
}
522+
}
523+
515524
ty
516525
}
517526

@@ -563,6 +572,10 @@ impl TypedArg<'_> {
563572
quote! {
564573
#name.val().unwrap_or(#default.into())
565574
}
575+
} else if self.variadic {
576+
quote! {
577+
&#name.variadic_vals()
578+
}
566579
} else if self.nullable {
567580
// Originally I thought we could just use the below case for `null` options, as
568581
// `val()` will return `Option<Option<T>>`, however, this isn't the case when

src/args.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub struct Arg<'a> {
2525
_type: DataType,
2626
as_ref: bool,
2727
allow_null: bool,
28-
variadic: bool,
28+
pub(crate) variadic: bool,
2929
default_value: Option<String>,
3030
zval: Option<&'a mut Zval>,
3131
variadic_zvals: Vec<Option<&'a mut Zval>>,
@@ -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 {
257+
min_num_args = min_num_args.saturating_sub(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: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,6 @@ impl<'a> FunctionBuilder<'a> {
127127
self
128128
}
129129

130-
/// Sets the function to be variadic
131-
pub fn variadic(mut self) -> Self {
132-
self.function.flags |= MethodFlags::Variadic.bits();
133-
self
134-
}
135-
136130
/// Sets the return value of the function.
137131
///
138132
/// # Parameters
@@ -163,10 +157,21 @@ impl<'a> FunctionBuilder<'a> {
163157
/// Returns a result containing the function entry if successful.
164158
pub fn build(mut self) -> Result<FunctionEntry> {
165159
let mut args = Vec::with_capacity(self.args.len() + 1);
160+
let mut n_req = self.n_req.unwrap_or(self.args.len());
161+
let variadic = self.args.last().is_some_and(|arg| arg.variadic);
162+
163+
if variadic {
164+
self.function.flags |= MethodFlags::Variadic.bits();
165+
n_req = n_req.saturating_sub(1);
166+
}
166167

167168
// argument header, retval etc
169+
// The first argument is used as `zend_internal_function_info` for the function.
170+
// That struct shares the same memory as `zend_internal_arg_info` which is used
171+
// for the arguments.
168172
args.push(ArgInfo {
169-
name: self.n_req.unwrap_or(self.args.len()) as *const _,
173+
// required_num_args
174+
name: n_req as *const _,
170175
type_: match self.retval {
171176
Some(retval) => {
172177
ZendType::empty_from_type(retval, self.ret_as_ref, false, self.ret_as_null)
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)