Repository: clap-rs/clap (rust)

Issue:
#[derive(Clap)] should support empty structs
### Describe your use case

This is obviously trivial compared to just doing `struct Args {}`, but it would be nice to maintain Rust's general consistency in allowing the same things for all three forms.

### Describe the solution you'd like

```
#[derive(Clap)]
struct UnitLikeStruct;

#[derive(Clap)]
struct EmptyTupleStruct();
```

### Additional context

```
error: `#[derive(Clap)]` only supports non-tuple structs and enums
   |
   | #[derive(Clap)]
   |          ^^^^
   |
```


Pull request:
Unit struct
<!--
If your PR closes some issues, please write `Closes #XXXX`
where `XXXX` is the number of the issue you want to fix.
Each issue goes on it's own line.
-->
Fixes #1705 

Patch:
diff --git a/bors.toml b/bors.toml
index e8871d13..aae74b98 100644
--- a/bors.toml
+++ b/bors.toml
@@ -10,4 +10,4 @@ timeout_sec = 7200
 prerun_timeout_sec = 7200
 delete_merged_branches = true
 cut_body_after = ""
-block_lables = ["R: blocked", "R: require changes"]
+block_lables = ["M: blocked", "M: require changes"]
diff --git a/clap_derive/src/derives/clap.rs b/clap_derive/src/derives/clap.rs
index 08fb5144..cca20aec 100644
--- a/clap_derive/src/derives/clap.rs
+++ b/clap_derive/src/derives/clap.rs
@@ -31,6 +31,17 @@ pub fn derive_clap(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
             dummies::clap_struct(ident);
             gen_for_struct(ident, &fields.named, &input.attrs)
         }
+        Struct(syn::DataStruct {
+            fields: syn::Fields::Unit,
+            ..
+        }) => {
+            dummies::clap_struct(ident);
+            gen_for_struct(
+                ident,
+                &punctuated::Punctuated::<syn::Field, token::Comma>::new(),
+                &input.attrs,
+            )
+        }
         Enum(ref e) => {
             dummies::clap_enum(ident);
             gen_for_enum(ident, &input.attrs, e)
diff --git a/clap_derive/src/derives/from_argmatches.rs b/clap_derive/src/derives/from_argmatches.rs
index b654e8c9..1201d931 100644
--- a/clap_derive/src/derives/from_argmatches.rs
+++ b/clap_derive/src/derives/from_argmatches.rs
@@ -11,46 +11,10 @@
 // This work was derived from Structopt (https://github.com/TeXitoi/structopt)
 // commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
 // MIT/Apache 2.0 license.
-use std::env;
-
-use proc_macro_error::abort_call_site;
 use quote::{quote, quote_spanned};
 use syn::{punctuated::Punctuated, spanned::Spanned, Token};
 
-use super::{
-    dummies, spanned::Sp, sub_type, Attrs, Kind, Name, ParserKind, Ty, DEFAULT_CASING,
-    DEFAULT_ENV_CASING,
-};
-
-pub fn derive_from_argmatches(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
-    use syn::Data::*;
-
-    let ident = &input.ident;
-    dummies::from_arg_matches(ident);
-
-    match input.data {
-        Struct(syn::DataStruct {
-            fields: syn::Fields::Named(ref fields),
-            ..
-        }) => {
-            let name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
-
-            let attrs = Attrs::from_struct(
-                proc_macro2::Span::call_site(),
-                &input.attrs,
-                Name::Assigned(quote!(#name)),
-                Sp::call_site(DEFAULT_CASING),
-                Sp::call_site(DEFAULT_ENV_CASING),
-            );
-
-            gen_for_struct(ident, &fields.named, &attrs)
-        }
-        Enum(_) => gen_for_enum(ident),
-        _ => {
-            abort_call_site!("#[derive(FromArgMatches)] only supports non-tuple structs and enums")
-        }
-    }
-}
+use super::{sub_type, Attrs, Kind, ParserKind, Ty};
 
 pub fn gen_constructor(
     fields: &Punctuated<syn::Field, Token![,]>,
diff --git a/clap_derive/src/derives/into_app.rs b/clap_derive/src/derives/into_app.rs
index aaef906a..a705b3b5 100644
--- a/clap_derive/src/derives/into_app.rs
+++ b/clap_derive/src/derives/into_app.rs
@@ -15,31 +15,16 @@
 use std::env;
 
 use proc_macro2::TokenStream;
-use proc_macro_error::{abort, abort_call_site};
+use proc_macro_error::abort;
 use quote::{quote, quote_spanned};
 use syn::{punctuated::Punctuated, spanned::Spanned, Token};
 
 use super::{
-    dummies, spanned::Sp, ty::Ty, Attrs, GenOutput, Kind, Name, ParserKind, DEFAULT_CASING,
+    spanned::Sp, ty::Ty, Attrs, GenOutput, Kind, Name, ParserKind, DEFAULT_CASING,
     DEFAULT_ENV_CASING,
 };
 use crate::derives::ty::sub_type;
 
-pub fn derive_into_app(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
-    use syn::Data::*;
-
-    dummies::into_app(&input.ident);
-
-    match input.data {
-        Struct(syn::DataStruct {
-            fields: syn::Fields::Named(ref fields),
-            ..
-        }) => gen_for_struct(&input.ident, &fields.named, &input.attrs).0,
-        Enum(_) => gen_for_enum(&input.ident),
-        _ => abort_call_site!("#[derive(IntoApp)] only supports non-tuple structs and enums"),
-    }
-}
-
 pub fn gen_for_struct(
     struct_name: &syn::Ident,
     fields: &Punctuated<syn::Field, Token![,]>,
diff --git a/clap_derive/src/derives/mod.rs b/clap_derive/src/derives/mod.rs
index c14387b2..115d9bf5 100644
--- a/clap_derive/src/derives/mod.rs
+++ b/clap_derive/src/derives/mod.rs
@@ -29,7 +29,4 @@ pub use self::attrs::{
     DEFAULT_ENV_CASING,
 };
 pub use self::clap::derive_clap;
-pub use self::from_argmatches::derive_from_argmatches;
-pub use self::into_app::derive_into_app;
-pub use self::subcommand::derive_subcommand;
 pub use self::ty::{sub_type, Ty};
diff --git a/clap_derive/src/derives/subcommand.rs b/clap_derive/src/derives/subcommand.rs
index 7c1973d2..6e167b08 100644
--- a/clap_derive/src/derives/subcommand.rs
+++ b/clap_derive/src/derives/subcommand.rs
@@ -1,4 +1,3 @@
-use super::dummies;
 use crate::derives::attrs::{Attrs, Kind, Name, DEFAULT_CASING, DEFAULT_ENV_CASING};
 use crate::derives::{from_argmatches, into_app, spanned::Sp};
 
@@ -6,23 +5,9 @@ use proc_macro2::{Ident, Span, TokenStream};
 use proc_macro_error::{abort, abort_call_site};
 use quote::{quote, quote_spanned};
 use syn::{
-    punctuated::Punctuated, spanned::Spanned, Attribute, Data, DataEnum, FieldsUnnamed, Token,
-    Variant,
+    punctuated::Punctuated, spanned::Spanned, Attribute, DataEnum, FieldsUnnamed, Token, Variant,
 };
 
-pub fn derive_subcommand(input: &syn::DeriveInput) -> TokenStream {
-    let name = &input.ident;
-    dummies::subcommand(name);
-
-    match input.data {
-        Data::Enum(ref e) => gen_for_enum(name, &input.attrs, e),
-        _ => abort_call_site!(
-            "`#[derive(Subcommand)]` supports only enums";
-            hint = "use `#[derive(Clap)]` for structs";
-        ),
-    }
-}
-
 pub fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream {
     let attrs = Attrs::from_struct(
         Span::call_site(),
diff --git a/clap_derive/src/lib.rs b/clap_derive/src/lib.rs
index fffabcaa..4bb373ae 100644
--- a/clap_derive/src/lib.rs
+++ b/clap_derive/src/lib.rs
@@ -36,27 +36,3 @@ pub fn clap(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let input: syn::DeriveInput = syn::parse_macro_input!(input);
     derives::derive_clap(&input).into()
 }
-
-/// Generates the `IntoApp` impl.
-#[proc_macro_derive(IntoApp, attributes(clap))]
-#[proc_macro_error]
-pub fn into_app(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
-    let input: syn::DeriveInput = syn::parse_macro_input!(input);
-    derives::derive_into_app(&input).into()
-}
-
-/// Generates the `FromArgMatches` impl.
-#[proc_macro_derive(FromArgMatches, attributes(clap))]
-#[proc_macro_error]
-pub fn from_arg_matches(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
-    let input: syn::DeriveInput = syn::parse_macro_input!(input);
-    derives::derive_from_argmatches(&input).into()
-}
-
-/// Generates the `Subcommand` impl.
-#[proc_macro_derive(Subcommand, attributes(clap))]
-#[proc_macro_error]
-pub fn subcommand(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
-    let input: syn::DeriveInput = syn::parse_macro_input!(input);
-    derives::derive_subcommand(&input).into()
-}


Test patch:
diff --git a/clap_derive/tests/basic.rs b/clap_derive/tests/basic.rs
index a3d31fbd..6091ef9a 100644
--- a/clap_derive/tests/basic.rs
+++ b/clap_derive/tests/basic.rs
@@ -28,3 +28,11 @@ fn basic() {
         Opt::parse_from(&["test", "-a24", "--arg", "42"])
     );
 }
+
+#[test]
+fn unit_struct() {
+    #[derive(Clap, PartialEq, Debug)]
+    struct Opt;
+
+    assert_eq!(Opt {}, Opt::parse_from(&["test"]));
+}
diff --git a/clap_derive/tests/privacy.rs b/clap_derive/tests/privacy.rs
index 6a36b953..d6692960 100644
--- a/clap_derive/tests/privacy.rs
+++ b/clap_derive/tests/privacy.rs
@@ -23,9 +23,9 @@ mod options {
 }
 
 mod subcommands {
-    use clap::Subcommand;
+    use clap::Clap;
 
-    #[derive(Debug, Subcommand)]
+    #[derive(Debug, Clap)]
     pub enum SubCommand {
         /// foo
         Foo {