Repository: pacak/bpaf (rust)

Issue:
Completions suggest unparseable commands
.. or some issue with groups i guess.

I have a parser that should switch between sub commands of different groups:

```

#[derive(Bpaf)]
enum Groups {
    GroupA(#[bpaf(external(group_a), group_help("Group A"))] GroupA),

    GroupBWrapper {
        #[bpaf(long, short)]
        shared: Option<String>,

        #[bpaf(external, group_help("Group B"))]
        group_b: GroupB,
    },
}

#[derive(Bpaf, Clone)]
enum GroupA {
    #[bpaf(command)]
    One,
    #[bpaf(command)]
    Two,
    #[bpaf(command)]
    Three,
}

#[derive(Bpaf, Clone)]

enum GroupB {
    #[bpaf(command)]
    Alpha,
    #[bpaf(command)]
    Beta,
    #[bpaf(command)]
    Gamma,
}

fn main() {
    let _ = groups().to_options().run();
}
```

Some groups may contain arguments that are supposed to come _before_ the commands but are only valid for said commands. That is in this case, with the `--shared` flag, only `GroupB` commands shall be valid.

The usage represents that somewhat:

```
Usage: (COMMAND ... | [-s ARG] COMMAND ...)

Available options:
    -s, --shared <ARG>
    -h, --help          Prints help information

Available commands:
  Group A
    one
    two
    three

  Group B
    alpha
    beta
    gamma
```

Yet, `--shared` is not under a `Group B` category.

Now when I try to complete sub commands I do get all subcommands and `--shared` (as expected)

```
$ prog <tab>
--shared <ARG>  beta            one             two                           
alpha           gamma           three 
```

However, once i complete with `--shared` the completion options still contain elements from `GroupA`.

```
$prog --shared bla <tab>
alpha  beta   gamma  one    three  two 
```

And in fact, `GroupA` commands are _not_ accepted:

```
$ prog --shared asd one 
Expected COMMAND ..., pass --help for usage information
```

- I'd expect `--shared` to be grouped under a `Group A` category
- I'd Expect some way to change `COMMMAND` to either the concrete values (see git's usage maybe) or change the placeholder per group
- with `--shared` only the completions for `GroupA` are valid, `GroupB` variants should not be suggested

Pull request description:
Release 0.7.8
- manpage generation bugfixes,
- internal cleanups
- avoid impossible shell completions

Fixes #162


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index c1f1471..c6610cc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "bpaf"
-version = "0.7.7"
+version = "0.7.8"
 edition = "2021"
 categories = ["command-line-interface"]
 description = "A simple Command Line Argument Parser with parser combinators"
diff --git a/Changelog.md b/Changelog.md
index 0aacb9e..23e8091 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -4,6 +4,7 @@
 - manpage generation bugfixes,
   thanks to @ysndr
 - internal cleanups
+- avoid impossible shell completions
 
 ## bpaf [0.7.7] - 2022-12-04
 - manpage generation
diff --git a/README.md b/README.md
index 875d208..08b5eb8 100644
--- a/README.md
+++ b/README.md
@@ -352,7 +352,7 @@ Usage --user <ARG>
  - `extradocs`: used internally to include tutorials to <https://docs.rs/bpaf>, no reason to enable it for local development unless you want to build your own copy of the documentation (<https://github.com/rust-lang/cargo/issues/8905>). Disabled by default.
 	
 	
- - `batteries`: helpers implemented with public `bpaf` API. Enabled by default.
+ - `batteries`: helpers implemented with public `bpaf` API. Disabled by default.
 	
 	
  - `autocomplete`: enables support for shell autocompletion. Disabled by default.
@@ -370,23 +370,27 @@ Usage --user <ARG>
 	Disabled by default.
 	
 	
+ - `manpage`: generate man page from help declaration, see [`OptionParser::as_manpage`][__link20]. Disabled by default.
+	
+	
 
 
- [__cargo_doc2readme_dependencies_info]: ggGkYW0AYXSEG52uRQSwBdezG6GWW8ODAbr5G6KRmT_WpUB5G9hPmBcUiIp6YXKEG-IPQtuM4VUFG47iETJwkSENG_GD7ukhn-KMG38J41C6yQn3YWSBgmRicGFmZTAuNy40
- [__link0]: https://docs.rs/bpaf/0.7.4/bpaf/?search=_derive_tutorial
- [__link1]: https://docs.rs/bpaf/0.7.4/bpaf/?search=_combinatoric_tutorial
- [__link10]: https://docs.rs/bpaf/0.7.4/bpaf/?search=parsers::NamedArg::switch
- [__link11]: https://docs.rs/bpaf/0.7.4/bpaf/?search=parsers::NamedArg::argument
- [__link12]: https://docs.rs/bpaf/0.7.4/bpaf/?search=params::positional
- [__link13]: https://docs.rs/bpaf/0.7.4/bpaf/?search=parsers::NamedArg::argument
- [__link14]: https://docs.rs/bpaf/0.7.4/bpaf/?search=params::positional
- [__link15]: https://docs.rs/bpaf/0.7.4/bpaf/?search=bpaf::Parser::complete
- [__link17]: https://docs.rs/bpaf/0.7.4/bpaf/?search=info::OptionParser::run_inner
- [__link2]: https://docs.rs/bpaf/0.7.4/bpaf/?search=_unusual
- [__link3]: https://docs.rs/bpaf/0.7.4/bpaf/?search=_applicative
- [__link4]: https://docs.rs/bpaf/0.7.4/bpaf/?search=batteries
+ [__cargo_doc2readme_dependencies_info]: ggGkYW0AYXSEG52uRQSwBdezG6GWW8ODAbr5G6KRmT_WpUB5G9hPmBcUiIp6YXKEGwet03UV33_nG7BNDOumJDcWG2J9NgcAg8w5G_uDmsZYklJQYWSBgmRicGFmZTAuNy43
+ [__link0]: https://docs.rs/bpaf/0.7.7/bpaf/?search=_derive_tutorial
+ [__link1]: https://docs.rs/bpaf/0.7.7/bpaf/?search=_combinatoric_tutorial
+ [__link10]: https://docs.rs/bpaf/0.7.7/bpaf/?search=parsers::NamedArg::switch
+ [__link11]: https://docs.rs/bpaf/0.7.7/bpaf/?search=parsers::NamedArg::argument
+ [__link12]: https://docs.rs/bpaf/0.7.7/bpaf/?search=params::positional
+ [__link13]: https://docs.rs/bpaf/0.7.7/bpaf/?search=parsers::NamedArg::argument
+ [__link14]: https://docs.rs/bpaf/0.7.7/bpaf/?search=params::positional
+ [__link15]: https://docs.rs/bpaf/0.7.7/bpaf/?search=bpaf::Parser::complete
+ [__link17]: https://docs.rs/bpaf/0.7.7/bpaf/?search=info::OptionParser::run_inner
+ [__link2]: https://docs.rs/bpaf/0.7.7/bpaf/?search=_unusual
+ [__link20]: https://docs.rs/bpaf/0.7.7/bpaf/?search=info::OptionParser::as_manpage
+ [__link3]: https://docs.rs/bpaf/0.7.7/bpaf/?search=_applicative
+ [__link4]: https://docs.rs/bpaf/0.7.7/bpaf/?search=batteries
  [__link5]: https://github.com/pacak/bpaf/discussions/categories/q-a
- [__link6]: https://docs.rs/bpaf/0.7.4/bpaf/?search=_derive_tutorial
- [__link7]: https://docs.rs/bpaf/0.7.4/bpaf/?search=_combinatoric_tutorial
- [__link8]: https://docs.rs/bpaf/0.7.4/bpaf/?search=_applicative
- [__link9]: https://docs.rs/bpaf/0.7.4/bpaf/?search=bpaf::Parser::fallback
+ [__link6]: https://docs.rs/bpaf/0.7.7/bpaf/?search=_derive_tutorial
+ [__link7]: https://docs.rs/bpaf/0.7.7/bpaf/?search=_combinatoric_tutorial
+ [__link8]: https://docs.rs/bpaf/0.7.7/bpaf/?search=_applicative
+ [__link9]: https://docs.rs/bpaf/0.7.7/bpaf/?search=bpaf::Parser::fallback
diff --git a/src/args.rs b/src/args.rs
index 95324e1..712286e 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -72,7 +72,7 @@ mod inner {
         pub(crate) no_pos_ahead: bool,
 
         #[cfg(feature = "autocomplete")]
-        pub(crate) comp: Option<crate::complete_gen::Complete>,
+        comp: Option<crate::complete_gen::Complete>,
 
         /// how many Ambiguities are there
         pub(crate) ambig: usize,
@@ -394,6 +394,38 @@ mod inner {
             self.items = Rc::from(new_items);
             Ok(())
         }
+
+        #[cfg(feature = "autocomplete")]
+        /// check if bpaf tries to complete last consumed element
+        pub(crate) fn touching_last_remove(&self) -> bool {
+            self.comp.is_some() && self.items.len() - 1 == self.current.unwrap_or(usize::MAX)
+        }
+
+        #[cfg(feature = "autocomplete")]
+        /// Check if current autocomplete head is valid
+        ///
+        /// Parsers preceeding current one must be able to consume all the items on the command
+        /// line: assuming usage line looking like this:
+        ///   ([-a] alpha) | beta
+        /// and user passes "-a <TAB>" we should not suggest "beta"
+        pub(crate) fn valid_complete_head(&self) -> bool {
+            self.len() == 0 || (self.len() == 1 && self.removed.last() == Some(&false))
+        }
+
+        #[cfg(feature = "autocomplete")]
+        pub(crate) fn comp_mut(&mut self) -> Option<&mut crate::complete_gen::Complete> {
+            self.comp.as_mut()
+        }
+
+        #[cfg(feature = "autocomplete")]
+        pub(crate) fn comp_ref(&self) -> Option<&crate::complete_gen::Complete> {
+            self.comp.as_ref()
+        }
+
+        #[cfg(feature = "autocomplete")]
+        pub(crate) fn swap_comps(&mut self, other: &mut Self) {
+            std::mem::swap(&mut self.comp, &mut other.comp);
+        }
     }
 
     pub(crate) struct ArgRangesIter<'a> {
@@ -438,9 +470,9 @@ pub use inner::*;
 impl Args {
     #[inline(never)]
     #[cfg(feature = "autocomplete")]
-    pub(crate) fn swap_comps(&mut self, comps: &mut Vec<crate::complete_gen::Comp>) {
-        if let Some(comp) = &mut self.comp {
-            std::mem::swap(comps, &mut comp.comps);
+    pub(crate) fn swap_comps_with(&mut self, comps: &mut Vec<crate::complete_gen::Comp>) {
+        if let Some(comp) = self.comp_mut() {
+            comp.swap_comps(comps);
         }
     }
 
@@ -556,12 +588,6 @@ impl Args {
     pub(crate) fn peek(&self) -> Option<&Arg> {
         self.items_iter().next().map(|x| x.1)
     }
-
-    #[cfg(feature = "autocomplete")]
-    /// check if bpaf tries to complete last consumed element
-    pub(crate) fn touching_last_remove(&self) -> bool {
-        self.comp.is_some() && self.items.len() - 1 == self.current.unwrap_or(usize::MAX)
-    }
 }
 
 #[cfg(test)]
diff --git a/src/complete_gen.rs b/src/complete_gen.rs
index f667f03..dc7dc72 100644
--- a/src/complete_gen.rs
+++ b/src/complete_gen.rs
@@ -26,7 +26,7 @@ use std::ffi::OsStr;
 #[derive(Clone, Debug)]
 pub(crate) struct Complete {
     /// completions accumulated so far
-    pub(crate) comps: Vec<Comp>,
+    comps: Vec<Comp>,
     pub(crate) output_rev: usize,
 }
 
@@ -42,10 +42,14 @@ impl Complete {
 impl Args {
     /// Add a new completion hint for flag, if needed
     pub(crate) fn push_flag(&mut self, named: &NamedArg) {
-        if let Some(comp) = &mut self.comp {
+        if !self.valid_complete_head() {
+            return;
+        }
+        let depth = self.depth;
+        if let Some(comp) = self.comp_mut() {
             comp.comps.push(Comp::Flag {
                 extra: CompExtra {
-                    depth: self.depth,
+                    depth,
                     hidden_group: "",
                     visible_group: "",
                     help: named.help.clone(),
@@ -57,10 +61,14 @@ impl Args {
 
     /// Add a new completion hint for an argument, if needed
     pub(crate) fn push_argument(&mut self, named: &NamedArg, metavar: &'static str) {
-        if let Some(comp) = &mut self.comp {
+        if !self.valid_complete_head() {
+            return;
+        }
+        let depth = self.depth;
+        if let Some(comp) = self.comp_mut() {
             comp.comps.push(Comp::Argument {
                 extra: CompExtra {
-                    depth: self.depth,
+                    depth,
                     hidden_group: "",
                     visible_group: "",
                     help: named.help.clone(),
@@ -78,10 +86,14 @@ impl Args {
         help: &Option<String>,
         is_arg: bool,
     ) {
-        if let Some(comp) = &mut self.comp {
+        if !self.valid_complete_head() {
+            return;
+        }
+        let depth = self.depth;
+        if let Some(comp) = self.comp_mut() {
             comp.comps.push(Comp::Positional {
                 extra: CompExtra {
-                    depth: self.depth,
+                    depth,
                     hidden_group: "",
                     visible_group: "",
                     help: help.clone(),
@@ -99,10 +111,14 @@ impl Args {
         short: Option<char>,
         help: &Option<String>,
     ) {
-        if let Some(comp) = &mut self.comp {
+        if !self.valid_complete_head() {
+            return;
+        }
+        let depth = self.depth;
+        if let Some(comp) = self.comp_mut() {
             comp.comps.push(Comp::Command {
                 extra: CompExtra {
-                    depth: self.depth,
+                    depth,
                     hidden_group: "",
                     visible_group: "",
                     help: help.clone(),
@@ -115,16 +131,20 @@ impl Args {
 
     /// Clear collected completions if enabled
     pub(crate) fn clear_comps(&mut self) {
-        if let Some(comp) = &mut self.comp {
+        if let Some(comp) = self.comp_mut() {
             comp.comps.clear();
         }
     }
 
     pub(crate) fn push_value(&mut self, body: &str, help: &Option<String>, is_arg: bool) {
-        if let Some(comp) = &mut self.comp {
+        if !self.valid_complete_head() {
+            return;
+        }
+        let depth = self.depth;
+        if let Some(comp) = self.comp_mut() {
             comp.comps.push(Comp::Value {
                 extra: CompExtra {
-                    depth: self.depth,
+                    depth,
                     hidden_group: "",
                     visible_group: "",
                     help: help.clone(),
@@ -134,6 +154,18 @@ impl Args {
             });
         }
     }
+
+    pub(crate) fn extend_with_style(&mut self, style: CompleteDecor, comps: &mut Vec<Comp>) {
+        if !self.valid_complete_head() {
+            return;
+        }
+        if let Some(comp) = self.comp_mut() {
+            for mut item in comps.drain(..) {
+                item.set_decor(style);
+                comp.comps.push(item);
+            }
+        }
+    }
 }
 impl Arg {
     pub(crate) fn is_word(&self) -> bool {
@@ -176,6 +208,22 @@ impl Complete {
             is_arg,
         });
     }
+
+    pub(crate) fn push_comp(&mut self, comp: Comp) {
+        self.comps.push(comp);
+    }
+
+    pub(crate) fn extend_comps(&mut self, comps: Vec<Comp>) {
+        self.comps.extend(comps);
+    }
+
+    pub(crate) fn drain_comps(&mut self) -> std::vec::Drain<Comp> {
+        self.comps.drain(0..)
+    }
+
+    pub(crate) fn swap_comps(&mut self, other: &mut Vec<Comp>) {
+        std::mem::swap(other, &mut self.comps);
+    }
 }
 
 #[derive(Clone, Debug)]
@@ -314,8 +362,13 @@ fn pair_to_os_string<'a>(pair: (&'a Arg, &'a OsStr)) -> Option<(&'a Arg, &'a str
 }
 
 impl Args {
+    /// Generate completion from collected heads
+    ///
+    /// before calling this method we run parser in "complete" mode and collect live heads inside
+    /// `self.comp`, this part goes over collected heads and generates possible completions from
+    /// that
     pub(crate) fn check_complete(&self) -> Result<(), Error> {
-        let comp = match &self.comp {
+        let comp = match self.comp_ref() {
             Some(comp) => comp,
             None => return Ok(()),
         };
diff --git a/src/complete_shell.rs b/src/complete_shell.rs
index 58d96b5..3ecad38 100644
--- a/src/complete_shell.rs
+++ b/src/complete_shell.rs
@@ -75,19 +75,20 @@ where
 
         // stash old completions
         let mut comp_items = Vec::new();
-        args.swap_comps(&mut comp_items);
+        args.swap_comps_with(&mut comp_items);
 
         let res = self.inner.eval(args);
 
         // at this point comp_items contains values added by the inner parser
-        args.swap_comps(&mut comp_items);
+        args.swap_comps_with(&mut comp_items);
 
-        if let Some(comp) = &mut args.comp {
+        let depth = args.depth;
+        if let Some(comp) = args.comp_mut() {
             for ci in comp_items {
                 if let Some(is_arg) = ci.meta_type() {
-                    comp.push_shell(self.op, args.depth, is_arg);
+                    comp.push_shell(self.op, depth, is_arg);
                 } else {
-                    comp.comps.push(ci);
+                    comp.push_comp(ci);
                 }
             }
         }
diff --git a/src/structs.rs b/src/structs.rs
index 7dc1d6b..6cbbf3a 100644
--- a/src/structs.rs
+++ b/src/structs.rs
@@ -125,13 +125,13 @@ where
         let mut comps = Vec::new();
 
         #[cfg(feature = "autocomplete")]
-        args.swap_comps(&mut comps);
+        args.swap_comps_with(&mut comps);
 
         #[allow(clippy::let_and_return)]
         let res = self.inner.eval(args);
 
         #[cfg(feature = "autocomplete")]
-        args.swap_comps(&mut comps);
+        args.swap_comps_with(&mut comps);
         res
     }
 
@@ -175,7 +175,7 @@ where
         #[cfg(feature = "autocomplete")]
         let mut comp_items = Vec::new();
         #[cfg(feature = "autocomplete")]
-        args.swap_comps(&mut comp_items);
+        args.swap_comps_with(&mut comp_items);
 
         // create forks for both branches, go with a successful one.
         // if they both fail - fallback to the original arguments
@@ -264,8 +264,8 @@ fn this_or_that_picks_first(
         std::cmp::Ordering::Less => {
             std::mem::swap(args, args_b);
             #[cfg(feature = "autocomplete")]
-            if let Some(comp) = &mut args.comp {
-                comp.comps.extend(comp_stash);
+            if let Some(comp) = args.comp_mut() {
+                comp.extend_comps(comp_stash);
             }
             return match err_b {
                 Some(err) => Err(err),
@@ -276,8 +276,8 @@ fn this_or_that_picks_first(
         std::cmp::Ordering::Greater => {
             std::mem::swap(args, args_a);
             #[cfg(feature = "autocomplete")]
-            if let Some(comp) = &mut args.comp {
-                comp.comps.extend(comp_stash);
+            if let Some(comp) = args.comp_mut() {
+                comp.extend_comps(comp_stash);
             }
             return match err_a {
                 Some(err) => Err(err),
@@ -287,9 +287,9 @@ fn this_or_that_picks_first(
     }
 
     #[cfg(feature = "autocomplete")]
-    if let (Some(a), Some(b)) = (&mut args_a.comp, &mut args_b.comp) {
-        comp_stash.extend(a.comps.drain(0..));
-        comp_stash.extend(b.comps.drain(0..));
+    if let (Some(a), Some(b)) = (args_a.comp_mut(), args_b.comp_mut()) {
+        comp_stash.extend(a.drain_comps());
+        comp_stash.extend(b.drain_comps());
     }
 
     // otherwise pick based on the left most or successful one
@@ -316,8 +316,8 @@ fn this_or_that_picks_first(
     };
 
     #[cfg(feature = "autocomplete")]
-    if let Some(comp) = &mut args.comp {
-        comp.comps.extend(comp_stash);
+    if let Some(comp) = args.comp_mut() {
+        comp.extend_comps(comp_stash);
     }
 
     res
@@ -374,7 +374,7 @@ where
             e @ Err(Error::Stderr(_) | Error::Stdout(_)) => e,
             Err(Error::Missing(_)) => {
                 #[cfg(feature = "autocomplete")]
-                std::mem::swap(&mut args.comp, &mut clone.comp);
+                args.swap_comps(&mut clone);
                 Ok(self.value.clone())
             }
         }
@@ -484,8 +484,8 @@ where
             std::mem::swap(args, &mut orig_args);
 
             #[cfg(feature = "autocomplete")]
-            if orig_args.comp.is_some() {
-                std::mem::swap(&mut args.comp, &mut orig_args.comp);
+            if orig_args.comp_mut().is_some() {
+                args.swap_comps(&mut orig_args);
             }
 
             match err {
@@ -637,16 +637,16 @@ where
     fn eval(&self, args: &mut Args) -> Result<T, Error> {
         // stash old
         let mut comp_items = Vec::new();
-        args.swap_comps(&mut comp_items);
+        args.swap_comps_with(&mut comp_items);
 
         let res = self.inner.eval(args);
 
         // restore old, now metavars added by inner parser, if any, are in comp_items
-        args.swap_comps(&mut comp_items);
+        args.swap_comps_with(&mut comp_items);
 
-        if let Some(comp) = &mut args.comp {
+        if let Some(comp) = &mut args.comp_mut() {
             if res.is_err() {
-                comp.comps.extend(comp_items);
+                comp.extend_comps(comp_items);
                 return res;
             }
         }
@@ -655,19 +655,20 @@ where
 
         // completion function generates suggestions based on the parsed inner value, for
         // that `res` must contain a parsed value
-        if let Some(comp) = &mut args.comp {
+        let depth = args.depth;
+        if let Some(comp) = &mut args.comp_mut() {
             for ci in comp_items {
                 if let Some(is_arg) = ci.meta_type() {
                     for (replacement, description) in (self.op)(&res) {
                         comp.push_value(
                             replacement.into(),
                             description.map(Into::into),
-                            args.depth,
+                            depth,
                             is_arg,
                         );
                     }
                 } else {
-                    comp.comps.push(ci);
+                    comp.push_comp(ci);
                 }
             }
         }
@@ -692,10 +693,10 @@ where
 {
     fn eval(&self, args: &mut Args) -> Result<T, Error> {
         let mut comp_items = Vec::new();
-        args.swap_comps(&mut comp_items);
+        args.swap_comps_with(&mut comp_items);
         let res = self.inner.eval(args);
-        args.swap_comps(&mut comp_items);
-        extend_with_args_style(args, self.style, &mut comp_items);
+        args.swap_comps_with(&mut comp_items);
+        args.extend_with_style(self.style, &mut comp_items);
         res
     }
 
@@ -704,20 +705,6 @@ where
     }
 }
 
-#[cfg(feature = "autocomplete")]
-fn extend_with_args_style(
-    args: &mut Args,
-    style: CompleteDecor,
-    comps: &mut Vec<crate::complete_gen::Comp>,
-) {
-    if let Some(comp) = &mut args.comp {
-        for mut item in comps.drain(..) {
-            item.set_decor(style);
-            comp.comps.push(item);
-        }
-    }
-}
-
 pub struct ParseAdjacent<P> {
     pub(crate) inner: P,
 }
@@ -801,12 +788,12 @@ where
                 return Ok(ok);
             } else {
                 #[cfg(feature = "autocomplete")]
-                this_arg.swap_comps(&mut comp_items);
+                this_arg.swap_comps_with(&mut comp_items);
             }
         }
 
         #[cfg(feature = "autocomplete")]
-        args.swap_comps(&mut comp_items);
+        args.swap_comps_with(&mut comp_items);
         Err(Error::Missing(vec![]))
     }
 


Test patch:
diff --git a/tests/completion.rs b/tests/completion.rs
index 4d48eda..48ea63a 100644
--- a/tests/completion.rs
+++ b/tests/completion.rs
@@ -399,7 +399,7 @@ fn static_complete_test_6() {
         .run_inner(Args::from(&["-b", "x", ""]).set_comp(1))
         .unwrap_err()
         .unwrap_stdout();
-    assert_eq!(r, "-a\n-b\n");
+    assert_eq!(r, "-b\n");
 }
 
 #[test]
@@ -1119,3 +1119,29 @@ fn bash_completion() {
     test_comp_v3(&parser, &["-a", ""], &[], 5);
     test_comp_v3(&parser, &["-a", ""], &[], 6);
 }
+
+#[test]
+fn generate_unparseable_items() {
+    let one = pure(()).to_options().command("cone");
+    let two = pure(()).to_options().command("ctwo");
+    let e = short('e').switch();
+
+    let one_e = construct!(e, one).map(|x| x.1);
+    let parser = construct!([one_e, two]).to_options();
+
+    // passing -e restricts branch with cmd_two
+    test_comp_v3(&parser, &["-e", ""], &["literal\tcone"], 4);
+    test_comp_v3(&parser, &["-e", "c"], &["literal\tcone"], 4);
+
+    // expected
+    test_comp_v3(
+        &parser,
+        &[""],
+        &[
+            "literal\t-e\tshow\t-e",
+            "literal\tcone\tshow\tcone",
+            "literal\tctwo\tshow\tctwo",
+        ],
+        4,
+    );
+}