Repository: clap-rs/clap (language: rust)

Issue:
`unused_qualifications` complains
### 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

### Rust Version

rustc 1.70.0 (90c541806 2023-05-31)

### Clap Version

4.3.1

### Minimal reproducible code

```rust
#![deny(unused_qualifications)]

use clap::Args;

#[derive(Args, Clone, Copy, Debug, Default)]
#[group(skip)]
pub struct Compose<L: Args> {
    #[command(flatten)]
    pub left: L,
}

fn main() {} 
```
[Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=fa1c15f5e1652d03974294c7bb6c9ecd)

### Steps to reproduce the bug with the above code

cargo check

### Actual Behaviour

```
error: unnecessary qualification
 --> src/main.rs:8:15
  |
8 |     #[command(flatten)]
  |               ^^^^^^^
  |
note: the lint level is defined here
 --> src/main.rs:1:9
  |
1 | #![deny(unused_qualifications)]
  |         ^^^^^^^^^^^^^^^^^^^^^
```

### Expected Behaviour

Compiles OK.

### Additional Context

Appears starting from 1.70 Rust. On 1.69 Rust everything is OK.

### Debug Output

If we run `cargo expand` on [Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=fa1c15f5e1652d03974294c7bb6c9ecd), we would see the expansion like this:
```rust
#[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
#[allow(clippy :: style, clippy :: complexity, clippy :: pedantic, clippy ::
restriction, clippy :: perf, clippy :: deprecated, clippy :: nursery, clippy
:: cargo, clippy :: suspicious_else_formatting, clippy :: almost_swapped,)]
impl<L: Args> clap::FromArgMatches for Compose<L> {
    // etc...
}
```

It seems, that starting from 1.70 Rust, the `unused_qualifications` became more clever and covers more code paths than it had done before. I wonder why the [`#[automatically_derived]` attribute](https://stackoverflow.com/questions/51481551/what-does-automatically-derived-mean) is not used in the macro expansions, which is used to handle most of such situations (modulo lint bugs)?


Discussion / hints:
Never heard of `#[automatically_derived]` before.  In Veykril/tlborm#93, I suggested it gets covered in the Little Book of Macros.

Fix patch:
diff --git a/clap_builder/src/builder/command.rs b/clap_builder/src/builder/command.rs
index 603942af488..62930766cb5 100644
--- a/clap_builder/src/builder/command.rs
+++ b/clap_builder/src/builder/command.rs
@@ -4030,7 +4030,7 @@ impl Command {
             }
             .to_owned();
 
-            for mut sc in &mut self.subcommands {
+            for sc in &mut self.subcommands {
                 debug!("Command::_build_bin_names:iter: bin_name set...");
 
                 if sc.usage_name.is_none() {
diff --git a/clap_derive/src/derives/args.rs b/clap_derive/src/derives/args.rs
index 83598f56477..20164ff2e24 100644
--- a/clap_derive/src/derives/args.rs
+++ b/clap_derive/src/derives/args.rs
@@ -86,7 +86,13 @@ pub fn gen_for_struct(
     };
 
     Ok(quote! {
-        #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
+        #[allow(
+            dead_code,
+            unreachable_code,
+            unused_variables,
+            unused_braces,
+            unused_qualifications,
+        )]
         #[allow(
             clippy::style,
             clippy::complexity,
@@ -99,6 +105,7 @@ pub fn gen_for_struct(
             clippy::suspicious_else_formatting,
             clippy::almost_swapped,
         )]
+        #[automatically_derived]
         impl #impl_generics clap::FromArgMatches for #item_name #ty_generics #where_clause {
             fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
                 Self::from_arg_matches_mut(&mut __clap_arg_matches.clone())
@@ -121,7 +128,13 @@ pub fn gen_for_struct(
             }
         }
 
-        #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
+        #[allow(
+            dead_code,
+            unreachable_code,
+            unused_variables,
+            unused_braces,
+            unused_qualifications,
+        )]
         #[allow(
             clippy::style,
             clippy::complexity,
@@ -134,6 +147,7 @@ pub fn gen_for_struct(
             clippy::suspicious_else_formatting,
             clippy::almost_swapped,
         )]
+        #[automatically_derived]
         impl #impl_generics clap::Args for #item_name #ty_generics #where_clause {
             fn group_id() -> Option<clap::Id> {
                 #group_id
diff --git a/clap_derive/src/derives/into_app.rs b/clap_derive/src/derives/into_app.rs
index 72f081fd8d1..0bd636245a9 100644
--- a/clap_derive/src/derives/into_app.rs
+++ b/clap_derive/src/derives/into_app.rs
@@ -29,7 +29,13 @@ pub fn gen_for_struct(
     let app_var = Ident::new("__clap_app", Span::call_site());
 
     let tokens = quote! {
-        #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
+        #[allow(
+            dead_code,
+            unreachable_code,
+            unused_variables,
+            unused_braces,
+            unused_qualifications,
+        )]
         #[allow(
             clippy::style,
             clippy::complexity,
@@ -42,6 +48,7 @@ pub fn gen_for_struct(
             clippy::suspicious_else_formatting,
             clippy::almost_swapped,
         )]
+        #[automatically_derived]
         impl #impl_generics clap::CommandFactory for #item_name #ty_generics #where_clause {
             fn command<'b>() -> clap::Command {
                 let #app_var = clap::Command::new(#name);
@@ -69,7 +76,13 @@ pub fn gen_for_enum(
     let app_var = Ident::new("__clap_app", Span::call_site());
 
     Ok(quote! {
-        #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
+        #[allow(
+            dead_code,
+            unreachable_code,
+            unused_variables,
+            unused_braces,
+            unused_qualifications,
+        )]
         #[allow(
             clippy::style,
             clippy::complexity,
@@ -82,6 +95,7 @@ pub fn gen_for_enum(
             clippy::suspicious_else_formatting,
             clippy::almost_swapped,
         )]
+        #[automatically_derived]
         impl #impl_generics clap::CommandFactory for #item_name #ty_generics #where_clause {
             fn command<'b>() -> clap::Command {
                 let #app_var = clap::Command::new(#name)
diff --git a/clap_derive/src/derives/parser.rs b/clap_derive/src/derives/parser.rs
index 926c50ce885..272948b63a9 100644
--- a/clap_derive/src/derives/parser.rs
+++ b/clap_derive/src/derives/parser.rs
@@ -86,6 +86,10 @@ fn gen_for_struct(
     let args = args::gen_for_struct(item, item_name, generics, fields)?;
 
     Ok(quote! {
+        #[automatically_derived]
+        #[allow(
+            unused_qualifications,
+        )]
         impl #impl_generics clap::Parser for #item_name #ty_generics #where_clause {}
 
         #into_app
@@ -105,6 +109,7 @@ fn gen_for_enum(
     let subcommand = subcommand::gen_for_enum(item, item_name, generics, variants)?;
 
     Ok(quote! {
+        #[automatically_derived]
         impl #impl_generics clap::Parser for #item_name #ty_generics #where_clause {}
 
         #into_app
diff --git a/clap_derive/src/derives/subcommand.rs b/clap_derive/src/derives/subcommand.rs
index 5c2350969ab..3ae74d01ad0 100644
--- a/clap_derive/src/derives/subcommand.rs
+++ b/clap_derive/src/derives/subcommand.rs
@@ -66,7 +66,13 @@ pub fn gen_for_enum(
     let has_subcommand = gen_has_subcommand(variants)?;
 
     Ok(quote! {
-        #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
+        #[allow(
+            dead_code,
+            unreachable_code,
+            unused_variables,
+            unused_braces,
+            unused_qualifications,
+        )]
         #[allow(
             clippy::style,
             clippy::complexity,
@@ -79,6 +85,7 @@ pub fn gen_for_enum(
             clippy::suspicious_else_formatting,
             clippy::almost_swapped,
         )]
+        #[automatically_derived]
         impl #impl_generics clap::FromArgMatches for #item_name #ty_generics #where_clause {
             fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
                 Self::from_arg_matches_mut(&mut __clap_arg_matches.clone())
@@ -92,7 +99,13 @@ pub fn gen_for_enum(
             #update_from_arg_matches
         }
 
-        #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
+        #[allow(
+            dead_code,
+            unreachable_code,
+            unused_variables,
+            unused_braces,
+            unused_qualifications,
+        )]
         #[allow(
             clippy::style,
             clippy::complexity,
@@ -105,6 +118,7 @@ pub fn gen_for_enum(
             clippy::suspicious_else_formatting,
             clippy::almost_swapped,
         )]
+        #[automatically_derived]
         impl #impl_generics clap::Subcommand for #item_name #ty_generics #where_clause {
             fn augment_subcommands <'b>(__clap_app: clap::Command) -> clap::Command {
                 #augmentation
diff --git a/clap_derive/src/derives/value_enum.rs b/clap_derive/src/derives/value_enum.rs
index 6f107c01c37..397eb332389 100644
--- a/clap_derive/src/derives/value_enum.rs
+++ b/clap_derive/src/derives/value_enum.rs
@@ -51,7 +51,13 @@ pub fn gen_for_enum(
     let to_possible_value = gen_to_possible_value(item, &lits);
 
     Ok(quote! {
-        #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
+        #[allow(
+            dead_code,
+            unreachable_code,
+            unused_variables,
+            unused_braces,
+            unused_qualifications,
+        )]
         #[allow(
             clippy::style,
             clippy::complexity,
@@ -64,6 +70,7 @@ pub fn gen_for_enum(
             clippy::suspicious_else_formatting,
             clippy::almost_swapped,
         )]
+        #[automatically_derived]
         impl clap::ValueEnum for #item_name {
             #value_variants
             #to_possible_value
diff --git a/clap_derive/src/dummies.rs b/clap_derive/src/dummies.rs
index b10bedc64a4..3a1581b23b5 100644
--- a/clap_derive/src/dummies.rs
+++ b/clap_derive/src/dummies.rs
@@ -7,6 +7,7 @@ use quote::quote;
 pub fn parser(name: &Ident) -> proc_macro2::TokenStream {
     let into_app = into_app(name);
     quote!(
+        #[automatically_derived]
         impl clap::Parser for #name {}
         #into_app
     )
@@ -15,6 +16,7 @@ pub fn parser(name: &Ident) -> proc_macro2::TokenStream {
 #[must_use]
 pub fn into_app(name: &Ident) -> proc_macro2::TokenStream {
     quote! {
+        #[automatically_derived]
         impl clap::CommandFactory for #name {
             fn command<'b>() -> clap::Command {
                 unimplemented!()
@@ -29,6 +31,7 @@ pub fn into_app(name: &Ident) -> proc_macro2::TokenStream {
 #[must_use]
 pub fn from_arg_matches(name: &Ident) -> proc_macro2::TokenStream {
     quote! {
+        #[automatically_derived]
         impl clap::FromArgMatches for #name {
             fn from_arg_matches(_m: &clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
                 unimplemented!()
@@ -44,6 +47,7 @@ pub fn from_arg_matches(name: &Ident) -> proc_macro2::TokenStream {
 pub fn subcommand(name: &Ident) -> proc_macro2::TokenStream {
     let from_arg_matches = from_arg_matches(name);
     quote! {
+        #[automatically_derived]
         impl clap::Subcommand for #name {
             fn augment_subcommands(_cmd: clap::Command) -> clap::Command {
                 unimplemented!()
@@ -63,6 +67,7 @@ pub fn subcommand(name: &Ident) -> proc_macro2::TokenStream {
 pub fn args(name: &Ident) -> proc_macro2::TokenStream {
     let from_arg_matches = from_arg_matches(name);
     quote! {
+        #[automatically_derived]
         impl clap::Args for #name {
             fn augment_args(_cmd: clap::Command) -> clap::Command {
                 unimplemented!()
@@ -78,6 +83,7 @@ pub fn args(name: &Ident) -> proc_macro2::TokenStream {
 #[must_use]
 pub fn value_enum(name: &Ident) -> proc_macro2::TokenStream {
     quote! {
+        #[automatically_derived]
         impl clap::ValueEnum for #name {
             fn value_variants<'a>() -> &'a [Self]{
                 unimplemented!()


Test patch:
diff --git a/tests/derive/deny_warnings.rs b/tests/derive/deny_warnings.rs
index 1b3fea2689f..086733c1c78 100644
--- a/tests/derive/deny_warnings.rs
+++ b/tests/derive/deny_warnings.rs
@@ -12,6 +12,7 @@
 // commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
 // MIT/Apache 2.0 license.
 
+#![deny(unused_qualifications)]
 #![deny(warnings)]
 
 use clap::Parser;
@@ -51,3 +52,16 @@ fn warning_never_enum() {
         Opt::try_parse_from(["test", "foo", "foo"]).unwrap()
     );
 }
+
+#[test]
+fn warning_unused_qualifications() {
+    // This causes `clap::Args` within the derive to be unused qualifications
+    use clap::Args;
+
+    #[derive(Args, Clone, Copy, Debug, Default)]
+    #[group(skip)]
+    pub struct Compose<L: Args> {
+        #[command(flatten)]
+        pub left: L,
+    }
+}