Skip to content

Commit 614f5b4

Browse files
committed
Support function args being passed by reference
Currently it's not possible for a function to accept args by reference. This PR adds early support for doing so, by transforming any function args that are `&mut T` into args that are passed by reference. E.g.: ``` \#[php_function] fn my_function( arg: &mut Zval ) { arg.reference_mut().map( |zval| zval.set_bool(true) ); } ```
1 parent 8b87e40 commit 614f5b4

File tree

1 file changed

+57
-3
lines changed

1 file changed

+57
-3
lines changed

crates/macros/src/function.rs

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub struct Arg {
2626
pub ty: String,
2727
pub nullable: bool,
2828
pub default: Option<String>,
29+
pub as_ref: bool,
2930
}
3031

3132
#[derive(Debug, Clone)]
@@ -249,12 +250,19 @@ pub fn get_return_type(output_type: &ReturnType) -> Result<Option<(String, bool)
249250
}
250251

251252
impl Arg {
252-
pub fn new(name: String, ty: String, nullable: bool, default: Option<String>) -> Self {
253+
pub fn new(
254+
name: String,
255+
ty: String,
256+
nullable: bool,
257+
default: Option<String>,
258+
as_ref: bool,
259+
) -> Self {
253260
Self {
254261
name,
255262
ty,
256263
nullable,
257264
default,
265+
as_ref,
258266
}
259267
}
260268

@@ -268,6 +276,7 @@ impl Arg {
268276
match ty {
269277
Type::Path(TypePath { path, .. }) => {
270278
let mut path = path.clone();
279+
let mut pass_by_ref = false;
271280
path.drop_lifetimes();
272281

273282
let seg = path.segments.last()?;
@@ -283,25 +292,69 @@ impl Arg {
283292
None
284293
}
285294
});
295+
296+
// For for types that are `Option<&mut T>` to turn them into `Option<&T>`, marking the Arg as
297+
// as "passed by reference".
298+
let option = Some(seg)
299+
.filter(|seg| seg.ident == "Option")
300+
.and_then(|seg| {
301+
if let PathArguments::AngleBracketed(args) = &seg.arguments {
302+
args.args
303+
.iter()
304+
.find(|arg| matches!(arg, GenericArgument::Type(_)))
305+
.map(|ga| {
306+
let new_ga = match ga {
307+
GenericArgument::Type(ty) => {
308+
let _rtype = match ty {
309+
Type::Reference(r) => {
310+
let mut new_ref = r.clone();
311+
new_ref.mutability = None;
312+
pass_by_ref = true;
313+
Type::Reference(new_ref)
314+
},
315+
_ => ty.clone(),
316+
};
317+
GenericArgument::Type(_rtype)
318+
},
319+
_ => ga.clone(),
320+
};
321+
new_ga.to_token_stream().to_string()
322+
})
323+
} else {
324+
None
325+
}
326+
});
327+
286328
let stringified = match result {
287329
Some(result) if is_return => result,
288-
_ => path.to_token_stream().to_string(),
330+
_ => match option {
331+
Some(result) => result,
332+
None => path.to_token_stream().to_string(),
333+
},
289334
};
290335

291336
Some(Arg::new(
292337
name,
293338
stringified,
294339
seg.ident == "Option" || default.is_some(),
295340
default,
341+
pass_by_ref,
296342
))
297343
}
298344
Type::Reference(ref_) => {
299345
// Returning references is invalid, so let's just create our arg
346+
347+
// Change any `&mut T` into `&T` and set the `as_ref` attribute on the Arg
348+
// to marked it as a "passed by ref" PHP argument.
349+
let mut ref_ = ref_.clone();
350+
let is_mutable_ref = ref_.mutability.is_some();
351+
ref_.mutability = None;
300352
Some(Arg::new(
301353
name,
302354
ref_.to_token_stream().to_string(),
303355
false,
304356
default,
357+
is_mutable_ref,
305358
))
306359
}
307360
_ => None,
@@ -361,14 +414,15 @@ impl Arg {
361414
let ty = self.get_type_ident();
362415

363416
let null = self.nullable.then(|| quote! { .allow_null() });
417+
let passed_by_ref = self.as_ref.then(|| quote! { .as_ref() });
364418
let default = self.default.as_ref().map(|val| {
365419
quote! {
366420
.default(#val)
367421
}
368422
});
369423

370424
quote! {
371-
::ext_php_rs::args::Arg::new(#name, #ty) #null #default
425+
::ext_php_rs::args::Arg::new(#name, #ty) #null #passed_by_ref #default
372426
}
373427
}
374428
}

0 commit comments

Comments
 (0)