Repository: pacak/bpaf (rust)

Issue:
Unable to derive `positional::<bool>` fields
I want to create a positional that can take boolean values, i.e. literally `true`, and `false`, no fancy parsing necessary.

I started off with this to parse a number in a related command:

```rust
#[derive(Bpaf)]
struct Args {
    #[bpaf(positional("a number"))]
    argument: u32
}
```

changing the `u32` to bool however does not seem to be possible

```rust
#[derive(Bpaf)]
struct Args {
    #[bpaf(positional("a bool"))]
    argument: bool
}
```

results in a compiler error

```
error: Can't derive consumer for unnamed boolean field
  --> crates/---/src/main.rs:71:15
   |
71 |     argument: bool
   |               ^^^^
```

approaches of setting a different parser similarly failed.

```rust
#[bpaf(positional("a bool"), parser(FromStr:from_str)]
#[bpaf(positional("a bool"), parser(bool::parse)]
```

the only thing i found to work is to manually create a parser for the field which is a bit unhandy and inconsistent:

```rust
#[derive(Bpaf)]
struct Args {
    #[bpaf(external(parse_bool)]
    argument: bool
}

fn parse_bool() -> impl Parser<bool> {
	positional("a bool")
}
```

Pull request description:
Positional bool
Fixes #171

@ysndr, can you take a look if this does what you want? If it is - I'll make a new release.

Todo:
- [x] Add `anywhere` top level annotation
- [x] Add `box` top level annotation
- [x] Add `catch` for `anywhere` and use it in failing test cases
- [x] update documentation

Patch:
diff --git a/Changelog.md b/Changelog.md
index 3e3d324..888bcc4 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -7,7 +7,11 @@
   command line and environment variables. If your code depends on
   the original behavior you should replace non failing parsers under
   `many` with failing parsers: `req_flag` instead of `switch`.
+- parsing combinators `many`, `some`, `optional` and `anywhere` will
+  now propagate parsing errors outwards, you can regain the old
+  behvior by specifying `catch`
 - support deriving `req_flag` consumers
+- support deriving `catch` annotation
 
 ## bpaf [0.7.10], bpaf_derive [0.3.5] - 2013-03-19
 - improve error messages for typos like `-llvm` instead of `--llvm`
diff --git a/bpaf_derive/src/top.rs b/bpaf_derive/src/top.rs
index b0376d3..ac076d2 100644
--- a/bpaf_derive/src/top.rs
+++ b/bpaf_derive/src/top.rs
@@ -46,6 +46,7 @@ enum BParser {
         fields: Fields,
         adjacent: bool,
         anywhere: bool,
+        catch: bool,
         boxed: bool,
     },
     Singleton(Box<ReqFlag>),
@@ -242,6 +243,7 @@ struct Outer {
     fallback: Option<Box<Expr>>,
     adjacent: bool,
     anywhere: bool,
+    catch: bool,
     boxed: bool,
 }
 
@@ -259,6 +261,7 @@ impl Outer {
             fallback: None,
             adjacent: false,
             anywhere: false,
+            catch: false,
             boxed: false,
         };
 
@@ -302,6 +305,12 @@ impl Outer {
                         res.adjacent = true;
                     } else if keyword == "anywhere" {
                         res.anywhere = true;
+                    } else if keyword == "catch" {
+                        if !res.anywhere {
+                            return Err(input_copy
+                                .error("catch only makes sense when used with `anywhere`"));
+                        }
+                        res.catch = true;
                     } else if keyword == "boxed" {
                         res.boxed = true;
                     } else if keyword == "command" {
@@ -426,6 +435,7 @@ impl Top {
             fields,
             adjacent: outer.adjacent,
             anywhere: outer.anywhere,
+            catch: outer.catch,
             boxed: outer.boxed,
         };
         Ok(Top {
@@ -468,6 +478,7 @@ impl Top {
                     fields,
                     adjacent: outer.adjacent,
                     anywhere: outer.anywhere,
+                    catch: outer.catch,
                     boxed: outer.boxed,
                 }
             } else if let Some(_cmd) = &inner.command {
@@ -476,6 +487,7 @@ impl Top {
                     fields: Fields::NoFields,
                     adjacent: outer.adjacent,
                     anywhere: outer.anywhere,
+                    catch: outer.catch,
                     boxed: outer.boxed,
                 }
             } else {
@@ -607,6 +619,7 @@ impl ToTokens for BParser {
                 fields,
                 adjacent,
                 anywhere,
+                catch,
                 boxed,
             } => {
                 if let Fields::NoFields = fields {
@@ -628,6 +641,9 @@ impl ToTokens for BParser {
                 if *anywhere {
                     quote!(.anywhere()).to_tokens(tokens);
                 }
+                if *catch {
+                    quote!(.catch()).to_tokens(tokens);
+                }
                 if *boxed {
                     quote!(.boxed()).to_tokens(tokens);
                 }
diff --git a/docs/src/anywhere_1/cases.txt b/docs/src/anywhere_1/cases.txt
index fbbb945..01d5f02 100644
--- a/docs/src/anywhere_1/cases.txt
+++ b/docs/src/anywhere_1/cases.txt
@@ -9,7 +9,7 @@ Options { multi_arg: Some(MultiArg { set: (), name: "name", value: "Bob" }), tur
 OK
 Options { multi_arg: Some(MultiArg { set: (), name: "name", value: "Bob" }), turbo: true }
 
-? But not in between, with potentially confusing error messages - to be fixed :)
+? But not in between
 > --set name --turbo Bob
 Stderr
---set is not expected in this context
+Expected an argument <ARG>, got --turbo
diff --git a/examples/dd.rs b/examples/dd.rs
index 7f01b97..315ae97 100644
--- a/examples/dd.rs
+++ b/examples/dd.rs
@@ -26,6 +26,7 @@ where
             Some(body) => T::from_str(body).map_err(|e| e.to_string()),
         })
         .anywhere()
+        .catch()
 }
 
 fn in_file() -> impl Parser<String> {
diff --git a/examples/find.rs b/examples/find.rs
index f079b97..e810b60 100644
--- a/examples/find.rs
+++ b/examples/find.rs
@@ -34,9 +34,9 @@ fn user() -> impl Parser<Option<String>> {
     let value = positional::<String>("USER");
     construct!(tag, value)
         .anywhere()
+        .catch()
         .map(|pair| pair.1)
         .optional()
-        .catch()
 }
 
 // parsers -exec xxx yyy zzz ;
@@ -52,9 +52,9 @@ fn exec() -> impl Parser<Option<Vec<OsString>>> {
     let endtag = any::<String>("END").guard(|s| s == ";", "not eot").hide();
     construct!(tag, item, endtag)
         .anywhere()
+        .catch()
         .map(|triple| triple.1)
         .optional()
-        .catch()
 }
 
 /// parses symbolic permissions `-perm -mode`, `-perm /mode` and `-perm mode`
@@ -87,9 +87,9 @@ fn perm() -> impl Parser<Option<Perm>> {
 
     construct!(tag, mode)
         .anywhere()
+        .catch()
         .map(|pair| pair.1)
         .optional()
-        .catch()
 }
 
 pub fn options() -> OptionParser<Options> {
diff --git a/examples/xorg.rs b/examples/xorg.rs
index de5ddf0..c9dba2e 100644
--- a/examples/xorg.rs
+++ b/examples/xorg.rs
@@ -26,6 +26,7 @@ fn toggle_options(name: &'static str, help: &'static str) -> impl Parser<bool> {
             }
         })
         .anywhere()
+        .catch()
 }
 
 fn extension() -> impl Parser<(String, bool)> {
@@ -50,7 +51,10 @@ fn extension() -> impl Parser<(String, bool)> {
 
     let state = construct!([on, off]);
     let name = positional::<String>("EXT").hide();
-    construct!(state, name).map(|(a, b)| (b, a)).anywhere()
+    construct!(state, name)
+        .map(|(a, b)| (b, a))
+        .anywhere()
+        .catch()
 }
 
 pub fn options() -> OptionParser<Options> {
diff --git a/src/args.rs b/src/args.rs
index e6fbd77..30ec85a 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -606,7 +606,10 @@ impl Args {
     ///
     /// returns Ok(None) if input is empty
     /// returns Err if first positional argument is a flag
-    pub(crate) fn take_positional_word(&mut self) -> Result<Option<(bool, OsString)>, Error> {
+    pub(crate) fn take_positional_word(
+        &mut self,
+        metavar: &'static str,
+    ) -> Result<Option<(bool, OsString)>, Error> {
         match self.items_iter().next() {
             Some((ix, Arg::PosWord(w))) => {
                 let w = w.clone();
@@ -620,7 +623,10 @@ impl Args {
                 self.remove(ix);
                 Ok(Some((false, w)))
             }
-            Some((_, arg)) => Err(Error::Message(format!("Expected an argument, got {}", arg))),
+            Some((_, arg)) => Err(Error::Message(format!(
+                "Expected an argument <{}>, got {}",
+                metavar, arg
+            ))),
             None => Ok(None),
         }
     }
@@ -659,7 +665,7 @@ mod tests {
         let flag = a.take_flag(&long("speed"));
         assert!(flag);
         assert!(!a.is_empty());
-        let s = a.take_positional_word().unwrap().unwrap();
+        let s = a.take_positional_word("SPEED").unwrap().unwrap();
         assert_eq!(s.1, "12");
         assert!(a.is_empty());
     }
@@ -754,7 +760,7 @@ mod tests {
     fn command_and_positional() {
         let mut a = Args::from(&["cmd", "pos"]);
         assert!(a.take_cmd("cmd"));
-        let w = a.take_positional_word().unwrap().unwrap();
+        let w = a.take_positional_word("A").unwrap().unwrap();
         assert_eq!(w.1, "pos");
         assert!(a.is_empty());
     }
@@ -763,7 +769,7 @@ mod tests {
     fn positionals_after_double_dash1() {
         let mut a = Args::from(&["-v", "--", "-x"]);
         assert!(a.take_flag(&short('v')));
-        let w = a.take_positional_word().unwrap().unwrap();
+        let w = a.take_positional_word("A").unwrap().unwrap();
         assert_eq!(w.1, "-x");
         assert!(a.is_empty());
     }
@@ -772,7 +778,7 @@ mod tests {
     fn positionals_after_double_dash2() {
         let mut a = Args::from(&["-v", "--", "-x"]);
         assert!(a.take_flag(&short('v')));
-        let w = a.take_positional_word().unwrap().unwrap();
+        let w = a.take_positional_word("A").unwrap().unwrap();
         assert_eq!(w.1, "-x");
         assert!(a.is_empty());
     }
@@ -782,7 +788,7 @@ mod tests {
         let mut a = Args::from(&["-v", "12", "--", "-x"]);
         let w = a.take_arg(&short('v'), false).unwrap().unwrap();
         assert_eq!(w, "12");
-        let w = a.take_positional_word().unwrap().unwrap();
+        let w = a.take_positional_word("A").unwrap().unwrap();
         assert_eq!(w.1, "-x");
         assert!(a.is_empty());
     }
diff --git a/src/docs/anywhere_1.md b/src/docs/anywhere_1.md
index 989027b..8669333 100644
--- a/src/docs/anywhere_1.md
+++ b/src/docs/anywhere_1.md
@@ -77,10 +77,10 @@ Other flags can go on either side of items
 Options { multi_arg: Some(MultiArg { set: (), name: "name", value: "Bob" }), turbo: true }
 ```
 
-But not in between, with potentially confusing error messages - to be fixed :)
+But not in between
 ```console
 % app --set name --turbo Bob
---set is not expected in this context
+Expected an argument <ARG>, got --turbo
 ```
 
 </details>
diff --git a/src/docs/dd.md b/src/docs/dd.md
index 368e732..165a782 100644
--- a/src/docs/dd.md
+++ b/src/docs/dd.md
@@ -27,6 +27,7 @@ where
             Some(body) => T::from_str(body).map_err(|e| e.to_string()),
         })
         .anywhere()
+        .catch()
 }
 
 fn in_file() -> impl Parser<String> {
diff --git a/src/docs/find.md b/src/docs/find.md
index 74fab83..d0f5619 100644
--- a/src/docs/find.md
+++ b/src/docs/find.md
@@ -35,9 +35,9 @@ fn user() -> impl Parser<Option<String>> {
     let value = positional::<String>("USER");
     construct!(tag, value)
         .anywhere()
+        .catch()
         .map(|pair| pair.1)
         .optional()
-        .catch()
 }
 
 // parsers -exec xxx yyy zzz ;
@@ -53,9 +53,9 @@ fn exec() -> impl Parser<Option<Vec<OsString>>> {
     let endtag = any::<String>("END").guard(|s| s == ";", "not eot").hide();
     construct!(tag, item, endtag)
         .anywhere()
+        .catch()
         .map(|triple| triple.1)
         .optional()
-        .catch()
 }
 
 /// parses symbolic permissions `-perm -mode`, `-perm /mode` and `-perm mode`
@@ -88,9 +88,9 @@ fn perm() -> impl Parser<Option<Perm>> {
 
     construct!(tag, mode)
         .anywhere()
+        .catch()
         .map(|pair| pair.1)
         .optional()
-        .catch()
 }
 
 pub fn options() -> OptionParser<Options> {
diff --git a/src/docs/xorg.md b/src/docs/xorg.md
index 9ddf89a..5350aed 100644
--- a/src/docs/xorg.md
+++ b/src/docs/xorg.md
@@ -27,6 +27,7 @@ fn toggle_options(name: &'static str, help: &'static str) -> impl Parser<bool> {
             }
         })
         .anywhere()
+        .catch()
 }
 
 fn extension() -> impl Parser<(String, bool)> {
@@ -51,7 +52,10 @@ fn extension() -> impl Parser<(String, bool)> {
 
     let state = construct!([on, off]);
     let name = positional::<String>("EXT").hide();
-    construct!(state, name).map(|(a, b)| (b, a)).anywhere()
+    construct!(state, name)
+        .map(|(a, b)| (b, a))
+        .anywhere()
+        .catch()
 }
 
 pub fn options() -> OptionParser<Options> {
diff --git a/src/lib.rs b/src/lib.rs
index 321caa9..fffefb5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -21,6 +21,7 @@
 //! - [Combinatoric tutorial](crate::_combinatoric_tutorial)
 //! - [Some very unusual cases](crate::_unusual)
 //! - [Applicative functors? What is it all about](crate::_applicative)
+//! - [Applicative parsing intro](https://rustmagazine.org/issue-2/applicative-parsing/)
 //! - [Batteries included](crate::batteries)
 //! - [Q&A](https://github.com/pacak/bpaf/discussions/categories/q-a)
 
@@ -1414,7 +1415,10 @@ pub trait Parser<T> {
     where
         Self: Sized + Parser<T>,
     {
-        ParseAnywhere { inner: self }
+        ParseAnywhere {
+            inner: self,
+            catch: false,
+        }
     }
 
     // consume
diff --git a/src/params.rs b/src/params.rs
index 3cdfe6a..33d23f3 100644
--- a/src/params.rs
+++ b/src/params.rs
@@ -870,7 +870,7 @@ fn parse_word(
     metavar: &'static str,
     help: &Option<String>,
 ) -> Result<OsString, Error> {
-    if let Some((is_strict, word)) = args.take_positional_word()? {
+    if let Some((is_strict, word)) = args.take_positional_word(metavar)? {
         if strict && !is_strict {
             #[cfg(feature = "autocomplete")]
             args.push_value("--", &Some("-- Positional only items".to_owned()), false);
diff --git a/src/structs.rs b/src/structs.rs
index f80ac9a..f2fca4b 100644
--- a/src/structs.rs
+++ b/src/structs.rs
@@ -1,5 +1,5 @@
 //! Structures that implement different methods on [`Parser`] trait
-use crate::{args::Conflict, info::Error, Args, Meta, Parser};
+use crate::{args::Conflict, info::Error, item::Item, Args, Meta, Parser};
 use std::marker::PhantomData;
 
 #[cfg(feature = "autocomplete")]
@@ -777,6 +777,24 @@ impl<T> Parser<T> for ParseBox<T> {
 #[doc = include_str!("docs/anywhere.md")]
 pub struct ParseAnywhere<P> {
     pub(crate) inner: P,
+    pub(crate) catch: bool,
+}
+
+impl<P> ParseAnywhere<P> {
+    #[must_use]
+    /// Handle parse failures
+    ///
+    /// Can be useful to decide to skip parsing of some items on a command line
+    /// When parser succeeds - `catch` version would return a value as usual
+    /// if it fails - `catch` would restore all the consumed values and return None.
+    ///
+    /// There's several structures that implement this attribute: [`ParseOptional`], [`ParseMany`]
+    /// and [`ParseSome`], behavior should be identical for all of them.
+    #[doc = include_str!("docs/catch.md")]
+    pub fn catch(mut self) -> Self {
+        self.catch = true;
+        self
+    }
 }
 
 impl<P, T> Parser<T> for ParseAnywhere<P>
@@ -787,22 +805,95 @@ where
         #[cfg(feature = "autocomplete")]
         let mut comp_items = Vec::new();
 
+        fn meta_items(meta: &Meta) -> Vec<Item> {
+            match meta {
+                Meta::And(xs) => {
+                    if xs.is_empty() {
+                        Vec::new()
+                    } else {
+                        meta_items(&xs[0])
+                    }
+                }
+                Meta::Or(xs) => {
+                    let mut res = Vec::new();
+                    for x in xs {
+                        res.extend(meta_items(x));
+                    }
+                    res
+                }
+                Meta::Item(i) => vec![i.clone()],
+                Meta::Optional(m) | Meta::Many(m) | Meta::Decorated(m, _) => meta_items(&*m),
+                Meta::Skip | Meta::HideUsage(_) => Vec::new(),
+            }
+        }
+
+        let mut best_missing = meta_items(&self.meta());
+
+        let mut best_consumed = 0;
+
         for (start, mut this_arg) in args.ranges() {
-            if let Ok(ok) = self.inner.eval(&mut this_arg) {
-                if start > 0 {
+            // since we only want to parse things to the right of the first item we perform
+            // parsing in two passes:
+            // - try to run the parser showing only single argument available at all the indices
+            // - try to run the parser showing starting at that argument and to the right of it
+            // this means constructing argument parsers from req flag and positional works as
+            // expected:
+            // consider examples "42 -n" and "-n 42"
+            // without multi step approach first command line also parses into 42
+
+            let mut scratch = this_arg.clone();
+            scratch.restrict_to_range(&(start..start + 1));
+            let before = scratch.len();
+            // nothing to consume, might as well stop right now
+            if before == 0 {
+                break;
+            }
+            // it will most likely fail, but it doesn't matter, we are only looking for the
+            // left most match
+            let _ = self.inner.eval(&mut scratch);
+            if before == scratch.len() {
+                // failed to consume anything which means we don't start parsing at this point
+                continue;
+            }
+
+            match self.inner.eval(&mut this_arg) {
+                // managed to consume something - should make changes permanent and return it
+                //
+                // ParseFailure covers failures or --help/--version for the nested parsers
+                // anywhere shouldn't consume that
+                good @ (Ok(_) | Err(Error::ParseFailure(_))) => {
                     this_arg.copy_usage_from(args, 0..start);
+                    std::mem::swap(&mut this_arg, args);
+                    return good;
+                }
+
+                // failed to find something, try to improve previous error message and resume
+                Err(Error::Missing(items)) => {
+                    let consumed = args.len() - this_arg.len();
+                    if consumed >= best_consumed {
+                        best_missing = items;
+                        best_consumed = consumed;
+                        #[cfg(feature = "autocomplete")]
+                        this_arg.swap_comps_with(&mut comp_items);
+                    }
+                }
+
+                // otherwise return the error message we get
+                otherwise @ Err(Error::Message(_)) => {
+                    let consumed = args.len() - this_arg.len();
+                    if consumed > 0 && !self.catch {
+                        this_arg.copy_usage_from(args, 0..start);
+                        #[cfg(feature = "autocomplete")]
+                        this_arg.swap_comps_with(&mut comp_items);
+                        return otherwise;
+                    }
                 }
-                std::mem::swap(&mut this_arg, args);
-                return Ok(ok);
-            } else {
-                #[cfg(feature = "autocomplete")]
-                this_arg.swap_comps_with(&mut comp_items);
             }
         }
 
         #[cfg(feature = "autocomplete")]
         args.swap_comps_with(&mut comp_items);
-        Err(Error::Missing(vec![]))
+        Err(Error::Missing(best_missing))
     }
 
     fn meta(&self) -> Meta {
@@ -815,7 +906,6 @@ where
 
 // anything else is
 fn classify_anywhere(meta: Meta) -> Meta {
-    use crate::item::Item;
     if let Meta::And(xs) = &meta {
         let mut fields = Vec::new();
         let mut iter = xs.iter();


Test patch:
diff --git a/bpaf_derive/src/field_tests.rs b/bpaf_derive/src/field_tests.rs
index 69b6b33..de60e9c 100644
--- a/bpaf_derive/src/field_tests.rs
+++ b/bpaf_derive/src/field_tests.rs
@@ -244,6 +244,42 @@ fn check_many_files_implicit() {
     assert_eq!(input.to_token_stream().to_string(), output.to_string());
 }
 
+#[test]
+fn many_catch() {
+    let input: NamedField = parse_quote! {
+        #[bpaf(catch)]
+        files: Vec<std::path::PathBuf>
+    };
+    let output = quote! {
+        ::bpaf::long("files").argument::<std::path::PathBuf>("ARG").many().catch()
+    };
+    assert_eq!(input.to_token_stream().to_string(), output.to_string());
+}
+
+#[test]
+fn option_catch() {
+    let input: NamedField = parse_quote! {
+        #[bpaf(catch)]
+        files: Option<std::path::PathBuf>
+    };
+    let output = quote! {
+        ::bpaf::long("files").argument::<std::path::PathBuf>("ARG").optional().catch()
+    };
+    assert_eq!(input.to_token_stream().to_string(), output.to_string());
+}
+
+#[test]
+fn some_catch() {
+    let input: NamedField = parse_quote! {
+        #[bpaf(argument("ARG"), some("files"), catch)]
+        files: Vec<std::path::PathBuf>
+    };
+    let output = quote! {
+        ::bpaf::long("files").argument::<std::path::PathBuf>("ARG").some("files").catch()
+    };
+    assert_eq!(input.to_token_stream().to_string(), output.to_string());
+}
+
 #[test]
 fn check_option_file_implicit() {
     let input: NamedField = parse_quote! {
diff --git a/bpaf_derive/src/top_tests.rs b/bpaf_derive/src/top_tests.rs
index c8d5b03..df2c1b1 100644
--- a/bpaf_derive/src/top_tests.rs
+++ b/bpaf_derive/src/top_tests.rs
@@ -725,6 +725,31 @@ fn anywhere_for_struct() {
     assert_eq!(top.to_token_stream().to_string(), expected.to_string());
 }
 
+#[test]
+fn anywhere_catch_for_struct() {
+    let top: Top = parse_quote! {
+        #[bpaf(adjacent, anywhere, catch)]
+        struct Opts {
+            a: String,
+            b: String,
+        }
+    };
+
+    let expected = quote! {
+        fn opts() -> impl ::bpaf::Parser<Opts> {
+            #[allow(unused_imports)]
+            use ::bpaf::Parser;
+            {
+                let a = ::bpaf::short('a').argument::<String>("ARG");
+                let b = ::bpaf::short('b').argument::<String>("ARG");
+                ::bpaf::construct!(Opts { a, b })
+            }.adjacent().anywhere().catch()
+        }
+    };
+
+    assert_eq!(top.to_token_stream().to_string(), expected.to_string());
+}
+
 #[test]
 fn box_for_struct() {
     let top: Top = parse_quote! {
diff --git a/tests/anywhere.rs b/tests/anywhere.rs
new file mode 100644
index 0000000..7ebef38
--- /dev/null
+++ b/tests/anywhere.rs
@@ -0,0 +1,154 @@
+use bpaf::*;
+
+#[test]
+fn parse_anywhere_no_catch() {
+    let a = short('a').req_flag(());
+    let b = positional::<usize>("x");
+    let ab = construct!(a, b).anywhere();
+    let c = short('c').switch();
+    let parser = construct!(ab, c).to_options();
+
+    let r = parser
+        .run_inner(Args::from(&["-a"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "Expected <x>, pass --help for usage information");
+
+    let r = parser
+        .run_inner(Args::from(&["-a", "221b"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "Couldn't parse \"221b\": invalid digit found in string");
+
+    let r = parser
+        .run_inner(Args::from(&["-c", "-a"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "Expected <x>, pass --help for usage information");
+
+    let r = parser
+        .run_inner(Args::from(&["-c", "-a", "221b"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "Couldn't parse \"221b\": invalid digit found in string");
+
+    let r = parser
+        .run_inner(Args::from(&["-a", "-c"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "Expected an argument <x>, got -c");
+
+    let r = parser
+        .run_inner(Args::from(&["-a", "221b", "-c"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "Couldn't parse \"221b\": invalid digit found in string");
+
+    let r = parser
+        .run_inner(Args::from(&["3", "-a"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "No such command: `3`, did you mean `-c`?");
+}
+
+#[test]
+fn parse_anywhere_catch_required() {
+    let a = short('a').req_flag(());
+    let b = positional::<usize>("x");
+    let ab = construct!(a, b).anywhere().catch();
+    let c = short('c').switch();
+    let parser = construct!(ab, c).to_options();
+
+    let r = parser
+        .run_inner(Args::from(&["-a"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "Expected <x>, pass --help for usage information");
+
+    let r = parser
+        .run_inner(Args::from(&["-a", "221b"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "Expected -a x, pass --help for usage information");
+
+    let r = parser
+        .run_inner(Args::from(&["-c", "-a"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "Expected <x>, pass --help for usage information");
+
+    let r = parser
+        .run_inner(Args::from(&["-c", "-a", "221b"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "Expected -a x, pass --help for usage information");
+
+    let r = parser
+        .run_inner(Args::from(&["-a", "-c"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "Expected -a x, pass --help for usage information");
+
+    let r = parser
+        .run_inner(Args::from(&["-a", "221b", "-c"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "Expected -a x, pass --help for usage information");
+
+    let r = parser
+        .run_inner(Args::from(&["3", "-a"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "No such command: `3`, did you mean `-c`?");
+}
+
+#[test]
+fn parse_anywhere_catch_optional() {
+    let a = short('a').req_flag(());
+    let b = positional::<usize>("x");
+    let ab = construct!(a, b).anywhere().catch().optional();
+    let c = short('c').switch();
+    let parser = construct!(ab, c).to_options();
+
+    let r = parser
+        .run_inner(Args::from(&["-a"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "-a is not expected in this context");
+
+    let r = parser
+        .run_inner(Args::from(&["-a", "221b"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "-a is not expected in this context");
+
+    let r = parser
+        .run_inner(Args::from(&["-c", "-a"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "-a is not expected in this context");
+
+    let r = parser
+        .run_inner(Args::from(&["-c", "-a", "221b"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "-a is not expected in this context");
+
+    let r = parser
+        .run_inner(Args::from(&["-a", "-c"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "-a is not expected in this context");
+
+    let r = parser
+        .run_inner(Args::from(&["-a", "221b", "-c"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "-a is not expected in this context");
+
+    let r = parser
+        .run_inner(Args::from(&["3", "-a"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "No such command: `3`, did you mean `-c`?");
+}