Skip to content

Commit e6dd339

Browse files
committed
fix(derive): Define multiple policy for Special Types
Before: - `bool`: a flag - `Option<_>`: not required - `Option<Option<_>>` is not required and when it is present, the value is not required - `Vec<_>`: multiple values, optional - `Option<Vec<_>>`: multiple values, min values of 0, optional After: - `bool`: a flag - `Option<_>`: not required - `Option<Option<_>>` is not required and when it is present, the value is not required - `Vec<_>`: multiple occurrences, optional - optional: `Vec` implies 0 or more, so should not imply required - `Option<Vec<_>>`: multiple occurrences, optional - optional: Use over `Vec` to detect when no option being present when using multiple values Motivations: My priorities were: 1. Are we getting in the users way? 2. Does the API make sense? 3. Does the API encourage best practices? I was originally concerned about the lack of composability with `Option<Option<_>>` and `Option<Vec<_>>` (and eventually `Vec<Vec<_>>`). It prescribes special meaning to each type depending on where it shows up, rather than providing a single meaning for a type generally. You then can't do things like have `Option<_>` mean "required argument with optional value" without hand constructing it. However, in practice the outer type correlates with the argument occurrence and the inner type with the value. It is rare to want the value behavior without also the occurrence behavior. So I figure it is probably fine as long as people can set the flags to manually get the behavior they want. `Vec<_>` implies multiple occurrences, rather than multiple values. Anecdotally, whenever I've used the old `Arg::multiple`, I thought I was getting `Arg::multiple_occurrences` only. `Arg::multiple_values`, without any bounds or delimiter requirement, can lead to a confusing user experience and isn't a good default for these. On top of that, if someone does have an unbounded or a delimiter multiple values, they are probably also using multiple occurrences. `Vec<_>` is optional because a `Vec` implies 0 or more, so we stick to the meaning of the rust type. At least for me, I also rarely need a required with multiple occurrences argument but more often need optional with multiple occurrences. `Option<Vec<_>>` ends up matching `Vec<_>` which can raise the question of why have it. Some users might prefer the type. Otherwise, this is so users can detect whether the argument is present or not when using `min_values(0)`. Rather than defining an entire policy around this and having users customize it, or setting `min_values(0)` without the rest of a default policy, this gives people a blank slate to work from. Another design option would have been to not infer any special-type settings if someone sets a handful of settings manually, which would have avoided the confusion in Issue clap-rs#2599 but I see that being confusing (for someone who knows the default, they will be expecting it to be additive; which flags disable inferred settings?) and brittle (as flags are added or changed, how do we ensure we keep this up?). Tests were added to ensure we support people customizing the behavior to match their needs. This is not solving: - `Vec<Vec<_>>`, see clap-rs#2924 - `(T1, T2)`, `Vec<(T1, T2)>`, etc, see clap-rs#1717 - `Vec<Option<_>>` and many other potential combinations Fixes clap-rs#1772 Fixes clap-rs#2599 See also clap-rs#2195
1 parent b9d007d commit e6dd339

File tree

3 files changed

+66
-14
lines changed

3 files changed

+66
-14
lines changed

clap_derive/src/derives/args.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,7 @@ pub fn gen_augment(
289289
Ty::OptionVec => quote_spanned! { ty.span()=>
290290
.takes_value(true)
291291
.value_name(#value_name)
292-
.multiple_values(true)
293-
.min_values(0)
292+
.multiple_occurrences(true)
294293
#possible_values
295294
#validator
296295
#allow_invalid_utf8
@@ -300,7 +299,7 @@ pub fn gen_augment(
300299
quote_spanned! { ty.span()=>
301300
.takes_value(true)
302301
.value_name(#value_name)
303-
.multiple_values(true)
302+
.multiple_occurrences(true)
304303
#possible_values
305304
#validator
306305
#allow_invalid_utf8
@@ -313,7 +312,6 @@ pub fn gen_augment(
313312

314313
Ty::Other if flag => quote_spanned! { ty.span()=>
315314
.takes_value(false)
316-
.multiple_values(false)
317315
},
318316

319317
Ty::Other => {

clap_derive/tests/arg_enum.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ fn vec_type() {
419419
Opt {
420420
arg: vec![ArgChoice::Foo, ArgChoice::Bar]
421421
},
422-
Opt::try_parse_from(&["", "-a", "foo", "bar"]).unwrap()
422+
Opt::try_parse_from(&["", "-a", "foo", "-a", "bar"]).unwrap()
423423
);
424424
assert!(Opt::try_parse_from(&["", "-a", "fOo"]).is_err());
425425
}
@@ -439,10 +439,6 @@ fn option_vec_type() {
439439
}
440440

441441
assert_eq!(Opt { arg: None }, Opt::try_parse_from(&[""]).unwrap());
442-
assert_eq!(
443-
Opt { arg: Some(vec![]) },
444-
Opt::try_parse_from(&["", "-a"]).unwrap()
445-
);
446442
assert_eq!(
447443
Opt {
448444
arg: Some(vec![ArgChoice::Foo])
@@ -453,7 +449,7 @@ fn option_vec_type() {
453449
Opt {
454450
arg: Some(vec![ArgChoice::Foo, ArgChoice::Bar])
455451
},
456-
Opt::try_parse_from(&["", "-a", "foo", "bar"]).unwrap()
452+
Opt::try_parse_from(&["", "-a", "foo", "-a", "bar"]).unwrap()
457453
);
458454
assert!(Opt::try_parse_from(&["", "-a", "fOo"]).is_err());
459455
}

clap_derive/tests/options.rs

+62-4
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ fn two_option_option_types() {
259259
}
260260

261261
#[test]
262-
fn vec_type_is_multiple_values() {
262+
fn vec_type_is_multiple_occurrences() {
263263
#[derive(Parser, PartialEq, Debug)]
264264
struct Opt {
265265
#[clap(short, long)]
@@ -270,6 +270,42 @@ fn vec_type_is_multiple_values() {
270270
Opt::try_parse_from(&["test", "-a24"]).unwrap()
271271
);
272272
assert_eq!(Opt { arg: vec![] }, Opt::try_parse_from(&["test"]).unwrap());
273+
assert_eq!(
274+
Opt { arg: vec![24, 42] },
275+
Opt::try_parse_from(&["test", "-a", "24", "-a", "42"]).unwrap()
276+
);
277+
}
278+
279+
#[test]
280+
fn vec_type_with_required() {
281+
#[derive(Parser, PartialEq, Debug)]
282+
struct Opt {
283+
#[clap(short, long, required = true)]
284+
arg: Vec<i32>,
285+
}
286+
assert_eq!(
287+
Opt { arg: vec![24] },
288+
Opt::try_parse_from(&["test", "-a24"]).unwrap()
289+
);
290+
assert!(Opt::try_parse_from(&["test"]).is_err());
291+
assert_eq!(
292+
Opt { arg: vec![24, 42] },
293+
Opt::try_parse_from(&["test", "-a", "24", "-a", "42"]).unwrap()
294+
);
295+
}
296+
297+
#[test]
298+
fn vec_type_with_multiple_values_only() {
299+
#[derive(Parser, PartialEq, Debug)]
300+
struct Opt {
301+
#[clap(short, long, multiple_values(true), multiple_occurrences(false))]
302+
arg: Vec<i32>,
303+
}
304+
assert_eq!(
305+
Opt { arg: vec![24] },
306+
Opt::try_parse_from(&["test", "-a24"]).unwrap()
307+
);
308+
assert_eq!(Opt { arg: vec![] }, Opt::try_parse_from(&["test"]).unwrap());
273309
assert_eq!(
274310
Opt { arg: vec![24, 42] },
275311
Opt::try_parse_from(&["test", "-a", "24", "42"]).unwrap()
@@ -308,6 +344,28 @@ fn option_vec_type() {
308344
Opt::try_parse_from(&["test", "-a", "1"]).unwrap()
309345
);
310346

347+
assert_eq!(
348+
Opt {
349+
arg: Some(vec![1, 2])
350+
},
351+
Opt::try_parse_from(&["test", "-a", "1", "-a", "2"]).unwrap()
352+
);
353+
354+
assert_eq!(Opt { arg: None }, Opt::try_parse_from(&["test"]).unwrap());
355+
}
356+
357+
#[test]
358+
fn option_vec_type_structopt_behavior() {
359+
#[derive(Parser, PartialEq, Debug)]
360+
struct Opt {
361+
#[clap(short, long, multiple_values(true), min_values(0))]
362+
arg: Option<Vec<i32>>,
363+
}
364+
assert_eq!(
365+
Opt { arg: Some(vec![1]) },
366+
Opt::try_parse_from(&["test", "-a", "1"]).unwrap()
367+
);
368+
311369
assert_eq!(
312370
Opt {
313371
arg: Some(vec![1, 2])
@@ -337,9 +395,9 @@ fn two_option_vec_types() {
337395
assert_eq!(
338396
Opt {
339397
arg: Some(vec![1]),
340-
b: Some(vec![])
398+
b: None,
341399
},
342-
Opt::try_parse_from(&["test", "-a", "1", "-b"]).unwrap()
400+
Opt::try_parse_from(&["test", "-a", "1"]).unwrap()
343401
);
344402

345403
assert_eq!(
@@ -355,7 +413,7 @@ fn two_option_vec_types() {
355413
arg: Some(vec![1, 2]),
356414
b: Some(vec![1, 2])
357415
},
358-
Opt::try_parse_from(&["test", "-a", "1", "2", "-b", "1", "2"]).unwrap()
416+
Opt::try_parse_from(&["test", "-a", "1", "-a", "2", "-b", "1", "-b", "2"]).unwrap()
359417
);
360418

361419
assert_eq!(

0 commit comments

Comments
 (0)