diff --git a/crates/macros/src/function.rs b/crates/macros/src/function.rs index 5e3def2432..5a389f1fe2 100644 --- a/crates/macros/src/function.rs +++ b/crates/macros/src/function.rs @@ -26,6 +26,7 @@ pub struct Arg { pub ty: String, pub nullable: bool, pub default: Option, + pub as_ref: bool, } #[derive(Debug, Clone)] @@ -249,12 +250,19 @@ pub fn get_return_type(output_type: &ReturnType) -> Result) -> Self { + pub fn new( + name: String, + ty: String, + nullable: bool, + default: Option, + as_ref: bool, + ) -> Self { Self { name, ty, nullable, default, + as_ref, } } @@ -268,6 +276,7 @@ impl Arg { match ty { Type::Path(TypePath { path, .. }) => { let mut path = path.clone(); + let mut pass_by_ref = false; path.drop_lifetimes(); let seg = path.segments.last()?; @@ -283,9 +292,45 @@ impl Arg { None } }); + + // For for types that are `Option<&mut T>` to turn them into `Option<&T>`, + // marking the Arg as as "passed by reference". + let option = Some(seg) + .filter(|seg| seg.ident == "Option") + .and_then(|seg| { + if let PathArguments::AngleBracketed(args) = &seg.arguments { + args.args + .iter() + .find(|arg| matches!(arg, GenericArgument::Type(_))) + .map(|ga| { + let new_ga = match ga { + GenericArgument::Type(ty) => { + let _rtype = match ty { + Type::Reference(r) => { + let mut new_ref = r.clone(); + new_ref.mutability = None; + pass_by_ref = true; + Type::Reference(new_ref) + } + _ => ty.clone(), + }; + GenericArgument::Type(_rtype) + } + _ => ga.clone(), + }; + new_ga.to_token_stream().to_string() + }) + } else { + None + } + }); + let stringified = match result { Some(result) if is_return => result, - _ => path.to_token_stream().to_string(), + _ => match option { + Some(result) => result, + None => path.to_token_stream().to_string(), + }, }; Some(Arg::new( @@ -293,6 +338,7 @@ impl Arg { stringified, seg.ident == "Option" || default.is_some(), default, + pass_by_ref, )) } Type::Reference(ref_) => { @@ -302,6 +348,7 @@ impl Arg { ref_.to_token_stream().to_string(), false, default, + ref_.mutability.is_some(), )) } _ => None, @@ -361,6 +408,7 @@ impl Arg { let 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 default = self.default.as_ref().map(|val| { quote! { .default(#val) @@ -368,7 +416,7 @@ impl Arg { }); quote! { - ::ext_php_rs::args::Arg::new(#name, #ty) #null #default + ::ext_php_rs::args::Arg::new(#name, #ty) #null #passed_by_ref #default } } } diff --git a/guide/src/types/bool.md b/guide/src/types/bool.md index 1974ff305a..498b40ec39 100644 --- a/guide/src/types/bool.md +++ b/guide/src/types/bool.md @@ -45,3 +45,17 @@ pub fn test_bool(input: bool) -> String { var_dump(test_bool(true)); // string(4) "Yes!" var_dump(test_bool(false)); // string(3) "No!" ``` + +## Rust example, taking by reference + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +# use ext_php_rs::prelude::*; +# use ext_php_rs::types; +#[php_function] +pub fn test_bool(input: &mut types::Zval) { + input.reference_mut().unwrap().set_bool(false); +} +# fn main() {} +```