Repository: clap-rs/clap (Rust)

Issue:
Derive: adding `visible_alias` attribute to complex subcommands results in duplicated aliases
StructOpt Issue: https://github.com/TeXitoi/structopt/issues/418
StructOpt PR: https://github.com/TeXitoi/structopt/pull/504

### Please complete the following tasks

- [X] I have searched the [discussions](https://github.com/clap-rs/clap/discussions)
- [X] I have searched the existing issues

### Rust Version

1.55.0

### Clap Version

3.0.0-beta.4

### Minimal reproducible code

```rust
use clap::Clap;

#[derive(Clap)]
enum Cmd {
    #[clap(visible_alias = "alias")]
    Test { args: Vec<String> },
}

#[derive(Clap)]
struct Cli {
    #[clap(subcommand)]
    cmd: Cmd,
}

fn main() {
    let cli = Cli::parse();
}
```


### Steps to reproduce the bug with the above code

cargo run -- --help

### Actual Behaviour

```console
FLAGS:
    -h, --help       Print help information
    -V, --version    Print version information

SUBCOMMANDS:
    help    Print this message or the help of the given subcommand(s)
    test     [aliases: alias, alias]
```

### Expected Behaviour

```console
FLAGS:
    -h, --help       Print help information
    -V, --version    Print version information

SUBCOMMANDS:
    help    Print this message or the help of the given subcommand(s)
    test     [aliases: alias]
```

### Additional Context

Output of `cargo expand` with duplicated calls to `visible_alias`:

```rust
impl clap::Subcommand for Cmd {
    fn augment_subcommands<'b>(app: clap::App<'b>) -> clap::App<'b> {
        let app = app;
        let app = app.subcommand({
            let subcommand = clap::App::new("test");
            let subcommand = subcommand;
            let subcommand = {
                let subcommand = subcommand;
                let subcommand = subcommand.arg(
                    clap::Arg::new("args")
                        .takes_value(true)
                        .value_name("ARGS")
                        .multiple_values(true)
                        .validator(|s| ::std::str::FromStr::from_str(s).map(|_: String| ())),
                );
                subcommand.visible_alias("alias")
            };
            subcommand.visible_alias("alias")
        });
        app
    }
```

### Debug Output

_No response_


Discussion:
@epage Might be fixed by your recent changes.
It isn't.  This was also recently reported for structopt.  You can see the proposed fix https://github.com/TeXitoi/structopt/pull/504
My proposed fix for #2894 should also fix this without having to duplicate logic like in the structopt fix (due to our divergence, the level of duplication is different, I don't remember if its worse or better).  So the question is whether we get to #2894 or go ahead and port structopt's fix over in the mean time.
I am of the opinion that we should focus on #2894 after the release and fix it correctly. Structopt's fixes are temporary because it is basically in maintenance mode.
#2894 involves a breaking change (just added the tag).  We'll have to change the trait definitions.

This is one of those things I classify as a non-disruptive breaking change.  Most users will be using the derive and will not notice the breaking change, only the bug fixes (and where you draw the line on behavior changes being breaking is another question)



Patch:
diff --git a/clap_derive/src/derives/subcommand.rs b/clap_derive/src/derives/subcommand.rs
index 6edb0c13ec4..e64d764e42b 100644
--- a/clap_derive/src/derives/subcommand.rs
+++ b/clap_derive/src/derives/subcommand.rs
@@ -240,14 +240,24 @@ fn gen_augment(
 
                 _ => {
                     let subcommand_var = Ident::new("__clap_subcommand", Span::call_site());
-                    let arg_block = match variant.fields {
+                    let sub_augment = match variant.fields {
                         Named(ref fields) => {
+                            // Defer to `gen_augment` for adding app methods
                             args::gen_augment(&fields.named, &subcommand_var, &attrs, override_required)
                         }
-                        Unit => quote!( #subcommand_var ),
+                        Unit => {
+                            let arg_block = quote!( #subcommand_var );
+                            let initial_app_methods = attrs.initial_top_level_methods();
+                            let final_from_attrs = attrs.final_top_level_methods();
+                            quote! {
+                                let #subcommand_var = #subcommand_var #initial_app_methods;
+                                let #subcommand_var = #arg_block;
+                                #subcommand_var #final_from_attrs
+                            }
+                        },
                         Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
                             let ty = &unnamed[0];
-                            if override_required {
+                            let arg_block = if override_required {
                                 quote_spanned! { ty.span()=>
                                     {
                                         <#ty as clap::Args>::augment_args_for_update(#subcommand_var)
@@ -259,6 +269,13 @@ fn gen_augment(
                                         <#ty as clap::Args>::augment_args(#subcommand_var)
                                     }
                                 }
+                            };
+                            let initial_app_methods = attrs.initial_top_level_methods();
+                            let final_from_attrs = attrs.final_top_level_methods();
+                            quote! {
+                                let #subcommand_var = #subcommand_var #initial_app_methods;
+                                let #subcommand_var = #arg_block;
+                                #subcommand_var #final_from_attrs
                             }
                         }
                         Unnamed(..) => {
@@ -267,14 +284,10 @@ fn gen_augment(
                     };
 
                     let name = attrs.cased_name();
-                    let initial_app_methods = attrs.initial_top_level_methods();
-                    let final_from_attrs = attrs.final_top_level_methods();
                     let subcommand = quote! {
                         let #app_var = #app_var.subcommand({
                             let #subcommand_var = clap::App::new(#name);
-                            let #subcommand_var = #subcommand_var #initial_app_methods;
-                            let #subcommand_var = #arg_block;
-                            #subcommand_var #final_from_attrs
+                            #sub_augment
                         });
                     };
                     Some(subcommand)


Test patch:
diff --git a/clap_derive/tests/issues.rs b/clap_derive/tests/issues.rs
index bdf797ccd85..1e4730c466f 100644
--- a/clap_derive/tests/issues.rs
+++ b/clap_derive/tests/issues.rs
@@ -75,6 +75,32 @@ fn issue_324() {
     assert!(help.contains("MY_VERSION"));
 }
 
+#[test]
+fn issue_418() {
+    #[derive(Debug, Parser)]
+    struct Opts {
+        #[clap(subcommand)]
+        /// The command to run
+        command: Command,
+    }
+
+    #[derive(Debug, Subcommand)]
+    enum Command {
+        /// Reticulate the splines
+        #[clap(visible_alias = "ret")]
+        Reticulate {
+            /// How many splines
+            num_splines: u8,
+        },
+        /// Frobnicate the rest
+        #[clap(visible_alias = "frob")]
+        Frobnicate,
+    }
+
+    let help = get_long_help::<Opts>();
+    assert!(help.contains("Reticulate the splines [aliases: ret]"));
+}
+
 #[test]
 fn issue_490() {
     use clap::Parser;