Repository: clap-rs/clap (rust)

Issue:
create skip_with_default attribute for Subcommands
### Please complete the following tasks

- [X] I have searched the [discussions](https://github.com/clap-rs/clap/discussions)
- [X] I have searched the [open](https://github.com/clap-rs/clap/issues) and [rejected](https://github.com/clap-rs/clap/issues?q=is%3Aissue+label%3AS-wont-fix+is%3Aclosed) issues

### Clap Version

4.0.17

### Describe your use case

The enum that I'm using with clap derive also serves as an external API so there are certain things it must have. It looks like this:

```rust
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Parser)]
#[serde(rename_all = "snake_case")]
pub enum Msg {
    #[command(subcommand)]
    Base(BaseMsg),
    #[clap(skip_with_default=my_default_function)] // This is the feature I would like to add
    Receive(ExternalStruct),
    AddAssets {
        #[arg(value_parser=serde_json::from_str::<[AssetInfo; 2]>)]
        assets: [AssetInfo; 2],
    },
```

Because Default is not implemented for ExternalStruct I cannot skip the variant regularly.

### Describe the solution you'd like

An attribute like 
```
#[clap(skip_with_default=my_default_function)]
```
Should work perfectly for me. I'm currently not aware of any other solution.

### Alternatives, if applicable

_No response_

### Additional Context

_No response_

Pull request:
fix(derive): Allow skipping enum variants with a value
Fixes #4411

<!--
Thanks for helping out!

Please link the appropriate issue from your PR.

If you don't have an issue, we'd recommend starting with one first so the PR can focus on the
implementation (unless its an obvious bug or documentation fix that will have
little conversation).
-->


Patch:
diff --git a/clap_derive/src/derives/subcommand.rs b/clap_derive/src/derives/subcommand.rs
index b437e42c..009684bf 100644
--- a/clap_derive/src/derives/subcommand.rs
+++ b/clap_derive/src/derives/subcommand.rs
@@ -354,11 +354,15 @@ fn gen_has_subcommand(variants: &[(&Variant, Item)]) -> TokenStream {
     let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants
         .iter()
         .filter_map(|(variant, item)| {
-            if let Kind::ExternalSubcommand = &*item.kind() {
-                ext_subcmd = true;
-                None
-            } else {
-                Some((variant, item))
+            let kind = item.kind();
+            match &*kind {
+                Kind::Skip(_, _) | Kind::Arg(_) | Kind::FromGlobal(_) | Kind::Value => None,
+
+                Kind::ExternalSubcommand => {
+                    ext_subcmd = true;
+                    None
+                }
+                Kind::Flatten(_) | Kind::Subcommand(_) | Kind::Command(_) => Some((variant, item)),
             }
         })
         .partition(|(_, item)| {
@@ -414,52 +418,56 @@ fn gen_from_arg_matches(variants: &[(&Variant, Item)]) -> TokenStream {
     let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants
         .iter()
         .filter_map(|(variant, item)| {
-            if let Kind::ExternalSubcommand = &*item.kind() {
-                if ext_subcmd.is_some() {
-                    abort!(
-                        item.kind().span(),
-                        "Only one variant can be marked with `external_subcommand`, \
+            let kind = item.kind();
+            match &*kind {
+                Kind::Skip(_, _) | Kind::Arg(_) | Kind::FromGlobal(_) | Kind::Value => None,
+
+                Kind::ExternalSubcommand => {
+                    if ext_subcmd.is_some() {
+                        abort!(
+                            item.kind().span(),
+                            "Only one variant can be marked with `external_subcommand`, \
                          this is the second"
-                    );
-                }
+                        );
+                    }
 
-                let ty = match variant.fields {
-                    Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty,
+                    let ty = match variant.fields {
+                        Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty,
 
-                    _ => abort!(
-                        variant,
-                        "The enum variant marked with `external_subcommand` must be \
+                        _ => abort!(
+                            variant,
+                            "The enum variant marked with `external_subcommand` must be \
                          a single-typed tuple, and the type must be either `Vec<String>` \
                          or `Vec<OsString>`."
-                    ),
-                };
-
-                let (span, str_ty) = match subty_if_name(ty, "Vec") {
-                    Some(subty) => {
-                        if is_simple_ty(subty, "String") {
-                            (subty.span(), quote!(::std::string::String))
-                        } else if is_simple_ty(subty, "OsString") {
-                            (subty.span(), quote!(::std::ffi::OsString))
-                        } else {
-                            abort!(
-                                ty.span(),
-                                "The type must be either `Vec<String>` or `Vec<OsString>` \
+                        ),
+                    };
+
+                    let (span, str_ty) = match subty_if_name(ty, "Vec") {
+                        Some(subty) => {
+                            if is_simple_ty(subty, "String") {
+                                (subty.span(), quote!(::std::string::String))
+                            } else if is_simple_ty(subty, "OsString") {
+                                (subty.span(), quote!(::std::ffi::OsString))
+                            } else {
+                                abort!(
+                                    ty.span(),
+                                    "The type must be either `Vec<String>` or `Vec<OsString>` \
                                  to be used with `external_subcommand`."
-                            );
+                                );
+                            }
                         }
-                    }
 
-                    None => abort!(
-                        ty.span(),
-                        "The type must be either `Vec<String>` or `Vec<OsString>` \
+                        None => abort!(
+                            ty.span(),
+                            "The type must be either `Vec<String>` or `Vec<OsString>` \
                          to be used with `external_subcommand`."
-                    ),
-                };
+                        ),
+                    };
 
-                ext_subcmd = Some((span, &variant.ident, str_ty));
-                None
-            } else {
-                Some((variant, item))
+                    ext_subcmd = Some((span, &variant.ident, str_ty));
+                    None
+                }
+                Kind::Flatten(_) | Kind::Subcommand(_) | Kind::Command(_) => Some((variant, item)),
             }
         })
         .partition(|(_, item)| {
@@ -563,10 +571,15 @@ fn gen_update_from_arg_matches(variants: &[(&Variant, Item)]) -> TokenStream {
     let (flatten, variants): (Vec<_>, Vec<_>) = variants
         .iter()
         .filter_map(|(variant, item)| {
-            match &*item.kind() {
+            let kind = item.kind();
+            match &*kind {
                 // Fallback to `from_arg_matches_mut`
-                Kind::ExternalSubcommand => None,
-                _ => Some((variant, item)),
+                Kind::Skip(_, _)
+                | Kind::Arg(_)
+                | Kind::FromGlobal(_)
+                | Kind::Value
+                | Kind::ExternalSubcommand => None,
+                Kind::Flatten(_) | Kind::Subcommand(_) | Kind::Command(_) => Some((variant, item)),
             }
         })
         .partition(|(_, item)| {


Test patch:
diff --git a/tests/derive/subcommands.rs b/tests/derive/subcommands.rs
index ad42616a..2252456f 100644
--- a/tests/derive/subcommands.rs
+++ b/tests/derive/subcommands.rs
@@ -532,8 +532,24 @@ fn skip_subcommand() {
         #[allow(dead_code)]
         #[command(skip)]
         Skip,
+
+        #[allow(dead_code)]
+        #[command(skip)]
+        Other(Other),
     }
 
+    #[allow(dead_code)]
+    #[derive(Debug, PartialEq, Eq)]
+    enum Other {
+        One,
+        Twp,
+    }
+
+    assert!(Subcommands::has_subcommand("add"));
+    assert!(Subcommands::has_subcommand("remove"));
+    assert!(!Subcommands::has_subcommand("skip"));
+    assert!(!Subcommands::has_subcommand("other"));
+
     assert_eq!(
         Opt::try_parse_from(&["test", "add"]).unwrap(),
         Opt {
@@ -553,6 +569,12 @@ fn skip_subcommand() {
         res.unwrap_err().kind(),
         clap::error::ErrorKind::InvalidSubcommand,
     );
+
+    let res = Opt::try_parse_from(&["test", "other"]);
+    assert_eq!(
+        res.unwrap_err().kind(),
+        clap::error::ErrorKind::InvalidSubcommand,
+    );
 }
 
 #[test]