Skip to content

Commit 07e8d32

Browse files
committed
Automatically check for optional parameters
No longer need to annotate the optional parameter
1 parent b1787eb commit 07e8d32

File tree

4 files changed

+92
-9
lines changed

4 files changed

+92
-9
lines changed

ext-php-rs-derive/src/function.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ pub fn parser(args: AttributeArgs, input: ItemFn) -> Result<(TokenStream, Functi
5555
Span::call_site(),
5656
);
5757
let args = build_args(inputs, &attr_args.defaults)?;
58+
let optional = find_optional_parameter(args.iter(), attr_args.optional);
5859
let arg_definitions = build_arg_definitions(&args);
59-
let arg_parser = build_arg_parser(args.iter(), &attr_args.optional)?;
60+
let arg_parser = build_arg_parser(args.iter(), &optional)?;
6061
let arg_accessors = build_arg_accessors(&args);
6162

6263
let return_handler = build_return_handler(output);
@@ -88,7 +89,7 @@ pub fn parser(args: AttributeArgs, input: ItemFn) -> Result<(TokenStream, Functi
8889
name: ident.to_string(),
8990
ident: internal_ident.to_string(),
9091
args,
91-
optional: attr_args.optional,
92+
optional,
9293
output: return_type,
9394
};
9495

@@ -131,6 +132,27 @@ fn build_arg_definitions(args: &[Arg]) -> Vec<TokenStream> {
131132
.collect()
132133
}
133134

135+
pub fn find_optional_parameter<'a>(
136+
args: impl DoubleEndedIterator<Item = &'a Arg>,
137+
optional: Option<String>,
138+
) -> Option<String> {
139+
if optional.is_some() {
140+
return optional;
141+
}
142+
143+
let mut optional = None;
144+
145+
for arg in args.rev() {
146+
if arg.nullable {
147+
optional.replace(arg.name.clone());
148+
} else {
149+
break;
150+
}
151+
}
152+
153+
optional
154+
}
155+
134156
pub fn build_arg_parser<'a>(
135157
args: impl Iterator<Item = &'a Arg>,
136158
optional: &Option<String>,
@@ -284,7 +306,12 @@ impl Arg {
284306
_ => path.to_token_stream().to_string(),
285307
};
286308

287-
Some(Arg::new(name, &stringified, seg.ident == "Option", default))
309+
Some(Arg::new(
310+
name,
311+
&stringified,
312+
seg.ident == "Option" || default.is_some(),
313+
default,
314+
))
288315
}
289316
Type::Reference(ref_) => {
290317
// Returning references is invalid, so let's just create our arg

ext-php-rs-derive/src/method.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ pub fn parser(input: &mut ImplItemMethod) -> Result<(TokenStream, Method)> {
5959

6060
let internal_ident = Ident::new(&format!("_internal_php_{}", ident), Span::call_site());
6161
let args = build_args(inputs, &defaults)?;
62+
let optional = function::find_optional_parameter(
63+
args.iter().filter_map(|arg| match arg {
64+
Arg::Typed(arg) => Some(arg),
65+
_ => None,
66+
}),
67+
optional,
68+
);
6269
let (arg_definitions, is_static) = build_arg_definitions(&args);
6370
let arg_parser = build_arg_parser(args.iter(), &optional)?;
6471
let arg_accessors = build_arg_accessors(&args);

guide/src/macros/function.md

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ return types.
88

99
## Optional parameters
1010

11-
Optional parameters can be used by setting the Rust parameter type to
12-
`Option<T>` and then passing the name of the first optional parameter into the
13-
macro options. Note that all parameters after the given parameter will be
14-
optional as well, and therefore must be of the type `Option<T>`.
11+
Optional parameters can be used by setting the Rust parameter type to a variant
12+
of `Option<T>`. The macro will then figure out which parameters are optional by
13+
using the last consecutive arguments that are a variant of `Option<T>` or have a
14+
default value.
1515

1616
```rust
1717
# extern crate ext_php_rs;
1818
# use ext_php_rs::prelude::*;
19-
#[php_function(optional = "age")]
19+
#[php_function]
2020
pub fn greet(name: String, age: Option<i32>) -> String {
2121
let mut greeting = format!("Hello, {}!", name);
2222

@@ -35,13 +35,59 @@ default, it does not need to be a variant of `Option`:
3535
```rust
3636
# extern crate ext_php_rs;
3737
# use ext_php_rs::prelude::*;
38-
#[php_function(optional = "offset", defaults(offset = 0))]
38+
#[php_function(defaults(offset = 0))]
3939
pub fn rusty_strpos(haystack: &str, needle: &str, offset: i64) -> Option<usize> {
4040
let haystack: String = haystack.chars().skip(offset as usize).collect();
4141
haystack.find(needle)
4242
}
4343
```
4444

45+
Note that if there is a non-optional argument after an argument that is a
46+
variant of `Option<T>`, the `Option<T>` argument will be deemed a nullable
47+
argument rather than an optional argument.
48+
49+
```rust
50+
# extern crate ext_php_rs;
51+
# use ext_php_rs::prelude::*;
52+
/// `age` will be deemed required and nullable rather than optional.
53+
#[php_function]
54+
pub fn greet(name: String, age: Option<i32>, description: String) -> String {
55+
let mut greeting = format!("Hello, {}!", name);
56+
57+
if let Some(age) = age {
58+
greeting += &format!(" You are {} years old.", age);
59+
}
60+
61+
greeting += &format!(" {}.", description);
62+
greeting
63+
}
64+
```
65+
66+
You can also specify the optional arguments if you want to have nullable
67+
arguments before optional arguments. This is done through an attribute
68+
parameter:
69+
70+
```rust
71+
# extern crate ext_php_rs;
72+
# use ext_php_rs::prelude::*;
73+
/// `age` will be deemed required and nullable rather than optional,
74+
/// while description will be optional.
75+
#[php_function(optional = "description")]
76+
pub fn greet(name: String, age: Option<i32>, description: Option<String>) -> String {
77+
let mut greeting = format!("Hello, {}!", name);
78+
79+
if let Some(age) = age {
80+
greeting += &format!(" You are {} years old.", age);
81+
}
82+
83+
if let Some(description) = description {
84+
greeting += &format!(" {}.", description);
85+
}
86+
87+
greeting
88+
}
89+
```
90+
4591
## Throwing exceptions
4692

4793
Exceptions can be thrown from inside a function which returns a `Result<T, E>`,

guide/src/macros/impl.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ attributes:
2727
- `#[public]`, `#[protected]` and `#[private]` - Sets the visibility of the
2828
method.
2929

30+
The `#[defaults]` and `#[optional]` attributes operate the same as the
31+
equivalent function attribute parameters.
32+
3033
## Constants
3134

3235
Constants are defined as regular Rust `impl` constants. Any type that implements

0 commit comments

Comments
 (0)