rust-lang / rust-clippy

A bunch of lints to catch common mistakes and improve your Rust code. Book: https://doc.rust-lang.org/clippy/
https://rust-lang.github.io/rust-clippy/
Other
11.01k stars 1.48k forks source link

duplicated_attributes: false positive with async-graphql Interface derive #12923

Open djc opened 2 weeks ago

djc commented 2 weeks ago

Summary

Don't have a stand-alone reproducible example yet, but I have this:

#[derive(Interface)]
#[graphql(field(
    name = "messages",
    ty = "Option<MessageConnection>",
    arg(name = "after", ty = "Option<String>"),
    arg(name = "before", ty = "Option<String>"),
    arg(name = "first", ty = "Option<i32>"),
    arg(name = "last", ty = "Option<i32>")
))]
#[graphql(field(name = "id", ty = "ID",))]
pub(super) enum Sender<'a> {
    User(User),
    Email(EmailSender<'a>),
}

See also here: https://async-graphql.github.io/async-graphql/en/define_interface.html.

Expanded macro output ```rust #[allow(clippy::all, clippy::pedantic)] impl<'a> Sender<'a> { #[inline] pub async fn r#messages<'ctx>( &self, ctx: &'ctx async_graphql::Context<'ctx>, arg0: Option, arg1: Option, arg2: Option, arg3: Option, ) -> async_graphql::Result> { match self { Sender::User(obj) => obj .r#messages(ctx, arg0, arg1, arg2, arg3) .await .map_err(|err| ::std::convert::Into::::into(err)) .map(::std::convert::Into::into), Sender::Email(obj) => obj .r#messages(ctx, arg0, arg1, arg2, arg3) .await .map_err(|err| ::std::convert::Into::::into(err)) .map(::std::convert::Into::into), } } #[inline] pub async fn r#id<'ctx>( &self, ctx: &'ctx async_graphql::Context<'ctx>, ) -> async_graphql::Result { match self { Sender::User(obj) => obj .r#id(ctx) .await .map_err(|err| ::std::convert::Into::::into(err)) .map(::std::convert::Into::into), Sender::Email(obj) => obj .r#id(ctx) .await .map_err(|err| ::std::convert::Into::::into(err)) .map(::std::convert::Into::into), } } } #[allow(clippy::all, clippy::pedantic)] #[async_graphql::async_trait::async_trait] impl<'a> async_graphql::resolver_utils::ContainerType for Sender<'a> { async fn resolve_field( &self, ctx: &async_graphql::Context<'_>, ) -> async_graphql::ServerResult<::std::option::Option> { if ctx.item.node.name.node == "messages" { let (_, arg0) = ctx.param_value::>("after", ::std::option::Option::None)?; let (_, arg1) = ctx.param_value::>("before", ::std::option::Option::None)?; let (_, arg2) = ctx.param_value::>("first", ::std::option::Option::None)?; let (_, arg3) = ctx.param_value::>("last", ::std::option::Option::None)?; let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); return async_graphql::OutputType::resolve( &self .r#messages(ctx, arg0, arg1, arg2, arg3) .await .map_err(|err| { ::std::convert::Into::::into(err) .into_server_error(ctx.item.pos) })?, &ctx_obj, ctx.item, ) .await .map(::std::option::Option::Some); } if ctx.item.node.name.node == "id" { let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); return async_graphql::OutputType::resolve( &self.r#id(ctx).await.map_err(|err| { ::std::convert::Into::::into(err) .into_server_error(ctx.item.pos) })?, &ctx_obj, ctx.item, ) .await .map(::std::option::Option::Some); } ::std::result::Result::Ok(::std::option::Option::None) } fn collect_all_fields<'__life>( &'__life self, ctx: &async_graphql::ContextSelectionSet<'__life>, fields: &mut async_graphql::resolver_utils::Fields<'__life>, ) -> async_graphql::ServerResult<()> { match self { Sender::User(obj) => obj.collect_all_fields(ctx, fields), Sender::Email(obj) => obj.collect_all_fields(ctx, fields), } } } #[allow(clippy::all, clippy::pedantic)] #[async_graphql::async_trait::async_trait] impl<'a> async_graphql::OutputType for Sender<'a> { fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { ::std::borrow::Cow::Borrowed("Sender") } fn introspection_type_name(&self) -> ::std::borrow::Cow<'static, ::std::primitive::str> { match self { Sender::User(obj) => ::type_name(), Sender::Email(obj) => as async_graphql::OutputType>::type_name(), } } fn create_type_info(registry: &mut async_graphql::registry::Registry) -> ::std::string::String { registry.create_output_type:: (async_graphql::registry::MetaTypeId::Interface, |registry|{ ::create_type_info(registry); registry.add_implements(& ::type_name(), ::std::convert::AsRef::as_ref(& ::std::borrow::Cow::Borrowed("Sender"))); as async_graphql::OutputType> ::create_type_info(registry); registry.add_implements(& as async_graphql::OutputType> ::type_name(), ::std::convert::AsRef::as_ref(& ::std::borrow::Cow::Borrowed("Sender"))); async_graphql::registry::MetaType::Interface { name: ::std::borrow::Cow::into_owned(::std::borrow::Cow::Borrowed("Sender")),description: ::std::option::Option::None,fields:{ let mut fields = async_graphql::indexmap::IndexMap::new(); fields.insert(::std::string::ToString::to_string("messages"),async_graphql::registry::MetaField { name: ::std::string::ToString::to_string("messages"),description: ::std::option::Option::None,args:{ let mut args = async_graphql::indexmap::IndexMap::new(); args.insert(::std::borrow::ToOwned::to_owned("after"),async_graphql::registry::MetaInputValue { name: ::std::string::ToString::to_string("after"),description: ::std::option::Option::None,ty: as async_graphql::InputType> ::create_type_info(registry),default_value: ::std::option::Option::None,visible: ::std::option::Option::None,inaccessible:false,tags: ::std::vec![],is_secret:false, }); args.insert(::std::borrow::ToOwned::to_owned("before"),async_graphql::registry::MetaInputValue { name: ::std::string::ToString::to_string("before"),description: ::std::option::Option::None,ty: as async_graphql::InputType> ::create_type_info(registry),default_value: ::std::option::Option::None,visible: ::std::option::Option::None,inaccessible:false,tags: ::std::vec![],is_secret:false, }); args.insert(::std::borrow::ToOwned::to_owned("first"),async_graphql::registry::MetaInputValue { name: ::std::string::ToString::to_string("first"),description: ::std::option::Option::None,ty: as async_graphql::InputType> ::create_type_info(registry),default_value: ::std::option::Option::None,visible: ::std::option::Option::None,inaccessible:false,tags: ::std::vec![],is_secret:false, }); args.insert(::std::borrow::ToOwned::to_owned("last"),async_graphql::registry::MetaInputValue { name: ::std::string::ToString::to_string("last"),description: ::std::option::Option::None,ty: as async_graphql::InputType> ::create_type_info(registry),default_value: ::std::option::Option::None,visible: ::std::option::Option::None,inaccessible:false,tags: ::std::vec![],is_secret:false, }); args },ty: as async_graphql::OutputType> ::create_type_info(registry),deprecation:async_graphql::registry::Deprecation::NoDeprecated,cache_control: ::std::default::Default::default(),external:false,provides: ::std::option::Option::None,requires: ::std::option::Option::None,shareable:false,inaccessible:false,tags: ::std::vec![],override_from: ::std::option::Option::None,visible: ::std::option::Option::None,compute_complexity: ::std::option::Option::None,directive_invocations: ::std::vec![], }); fields.insert(::std::string::ToString::to_string("id"),async_graphql::registry::MetaField { name: ::std::string::ToString::to_string("id"),description: ::std::option::Option::None,args:{ let mut args = async_graphql::indexmap::IndexMap::new(); args },ty: ::create_type_info(registry),deprecation:async_graphql::registry::Deprecation::NoDeprecated,cache_control: ::std::default::Default::default(),external:false,provides: ::std::option::Option::None,requires: ::std::option::Option::None,shareable:false,inaccessible:false,tags: ::std::vec![],override_from: ::std::option::Option::None,visible: ::std::option::Option::None,compute_complexity: ::std::option::Option::None,directive_invocations: ::std::vec![], }); fields },possible_types:{ let mut possible_types = async_graphql::indexmap::IndexSet::new(); possible_types.insert( ::type_name().into_owned()); possible_types.insert(as async_graphql::OutputType> ::type_name().into_owned()); possible_types },extends:false,keys: ::std::option::Option::None,visible: ::std::option::Option::None,inaccessible:false,tags: ::std::vec![],rust_typename: ::std::option::Option::Some(::std::any::type_name:: ()), } }) } async fn resolve( &self, ctx: &async_graphql::ContextSelectionSet<'_>, _field: &async_graphql::Positioned, ) -> async_graphql::ServerResult { async_graphql::resolver_utils::resolve_container(ctx, self).await } } impl<'a> async_graphql::InterfaceType for Sender<'a> {} ```

Lint Name

duplicated_attributes

Reproducer

No response

Version

rustc 1.79.0 (129f3b996 2024-06-10)
binary: rustc
commit-hash: 129f3b9964af4d4a709d1383930ade12dfe7c081
commit-date: 2024-06-10
host: aarch64-apple-darwin
release: 1.79.0
LLVM version: 18.1.7

cc @GuillaumeGomez (who seems to have created this lint)

GuillaumeGomez commented 2 weeks ago

Do you have the error too please?

djc commented 2 weeks ago

Warnings:

warning: duplicated attribute
  --> epoxide/src/graphql/message.rs:16:26
   |
16 |     arg(name = "before", ty = "Option<String>"),
   |                          ^^^^^^^^^^^^^^^^^^^^^
   |
note: first defined here
  --> epoxide/src/graphql/message.rs:15:25
   |
15 |     arg(name = "after", ty = "Option<String>"),
   |                         ^^^^^^^^^^^^^^^^^^^^^
help: remove this attribute
  --> epoxide/src/graphql/message.rs:16:26
   |
16 |     arg(name = "before", ty = "Option<String>"),
   |                          ^^^^^^^^^^^^^^^^^^^^^
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#duplicated_attributes
   = note: `#[warn(clippy::duplicated_attributes)]` on by default

warning: duplicated attribute
  --> epoxide/src/graphql/message.rs:18:24
   |
18 |     arg(name = "last", ty = "Option<i32>")
   |                        ^^^^^^^^^^^^^^^^^^
   |
note: first defined here
  --> epoxide/src/graphql/message.rs:17:25
   |
17 |     arg(name = "first", ty = "Option<i32>"),
   |                         ^^^^^^^^^^^^^^^^^^
help: remove this attribute
  --> epoxide/src/graphql/message.rs:18:24
   |
18 |     arg(name = "last", ty = "Option<i32>")
   |                        ^^^^^^^^^^^^^^^^^^
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#duplicated_attributes

warning: `epoxide` (lib) generated 2 warnings
dewert99 commented 1 week ago

ambassador (0.3.7) seems to have a similar problem (see https://github.com/hobofan/ambassador/issues/57).

I've tried to make a more minimal example:

#![allow(unused)]
use ambassador::{delegatable_trait, Delegate};

#[delegatable_trait]
trait Trait1 {}

#[delegatable_trait]
trait Trait2 {}

#[derive(Delegate)]
#[delegate(Trait1, target = "0")]
#[delegate(Trait2, target = "0")]
struct S<X>(X);

fn main() {}

cargo expand

#![feature(prelude_import)]
#![allow(unused)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use ambassador::{delegatable_trait, Delegate};
trait Trait1 {}
trait Trait2 {}
#[delegate(Trait1, target = "0")]
#[delegate(Trait2, target = "0")]
struct S<X>(X);
impl<X> Trait1 for S<X>
where
    X: Trait1,
{}
impl<X> Trait2 for S<X>
where
    X: Trait2,
{}
fn main() {}

cargo +nightly clippy

warning: duplicated attribute
  --> src/main.rs:12:20
   |
12 | #[delegate(Trait2, target = "0")]
   |                    ^^^^^^^^^^^^
   |
note: first defined here
  --> src/main.rs:11:20
   |
11 | #[delegate(Trait1, target = "0")]
   |                    ^^^^^^^^^^^^
help: remove this attribute
  --> src/main.rs:12:20
   |
12 | #[delegate(Trait2, target = "0")]
   |                    ^^^^^^^^^^^^
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#duplicated_attributes
   = note: `#[warn(clippy::duplicated_attributes)]` on by default

Would it be possible to not run the lint on derive macro helper attributes?