Detect prefixed attributes as duplicated

This commit is contained in:
newt 2025-07-25 00:34:01 +01:00
parent 60a978d2e9
commit da6618097f
9 changed files with 89 additions and 103 deletions

View file

@ -2,6 +2,7 @@ use super::DUPLICATED_ATTRIBUTES;
use clippy_utils::diagnostics::span_lint_and_then;
use itertools::Itertools;
use rustc_ast::{Attribute, MetaItem};
use rustc_ast_pretty::pprust::path_to_string;
use rustc_data_structures::fx::FxHashMap;
use rustc_lint::EarlyContext;
use rustc_span::{Span, Symbol, sym};
@ -35,31 +36,38 @@ fn check_duplicated_attr(
if attr.span.from_expansion() {
return;
}
let Some(ident) = attr.ident() else { return };
let name = ident.name;
if name == sym::doc || name == sym::cfg_attr_trace || name == sym::rustc_on_unimplemented || name == sym::reason {
// FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg
// conditions are the same.
// `#[rustc_on_unimplemented]` contains duplicated subattributes, that's expected.
return;
}
if let Some(direct_parent) = parent.last()
&& *direct_parent == sym::cfg_trace
&& [sym::all, sym::not, sym::any].contains(&name)
{
// FIXME: We don't correctly check `cfg`s for now, so if it's more complex than just a one
// level `cfg`, we leave.
return;
let attr_path = if let Some(ident) = attr.ident() {
ident.name
} else {
Symbol::intern(&path_to_string(&attr.path))
};
if let Some(ident) = attr.ident() {
let name = ident.name;
if name == sym::doc || name == sym::cfg_attr_trace || name == sym::rustc_on_unimplemented || name == sym::reason
{
// FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg
// conditions are the same.
// `#[rustc_on_unimplemented]` contains duplicated subattributes, that's expected.
return;
}
if let Some(direct_parent) = parent.last()
&& *direct_parent == sym::cfg_trace
&& [sym::all, sym::not, sym::any].contains(&name)
{
// FIXME: We don't correctly check `cfg`s for now, so if it's more complex than just a one
// level `cfg`, we leave.
return;
}
}
if let Some(value) = attr.value_str() {
emit_if_duplicated(
cx,
attr,
attr_paths,
format!("{}:{name}={value}", parent.iter().join(":")),
format!("{}:{attr_path}={value}", parent.iter().join(":")),
);
} else if let Some(sub_attrs) = attr.meta_item_list() {
parent.push(name);
parent.push(attr_path);
for sub_attr in sub_attrs {
if let Some(meta) = sub_attr.meta_item() {
check_duplicated_attr(cx, meta, attr_paths, parent);
@ -67,7 +75,7 @@ fn check_duplicated_attr(
}
parent.pop();
} else {
emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}", parent.iter().join(":")));
emit_if_duplicated(cx, attr, attr_paths, format!("{}:{attr_path}", parent.iter().join(":")));
}
}

View file

@ -1,6 +1,6 @@
//@aux-build:proc_macro_attr.rs
#![warn(clippy::duplicated_attributes, clippy::duplicated_attributes)] //~ ERROR: duplicated attribute
#![feature(rustc_attrs)]
#![warn(clippy::duplicated_attributes)]
#![cfg(any(unix, windows))]
#![allow(dead_code)]
#![allow(dead_code)] //~ ERROR: duplicated attribute

View file

@ -1,3 +1,22 @@
error: duplicated attribute
--> tests/ui/duplicated_attributes.rs:2:40
|
LL | #![warn(clippy::duplicated_attributes, clippy::duplicated_attributes)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: first defined here
--> tests/ui/duplicated_attributes.rs:2:9
|
LL | #![warn(clippy::duplicated_attributes, clippy::duplicated_attributes)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: remove this attribute
--> tests/ui/duplicated_attributes.rs:2:40
|
LL | #![warn(clippy::duplicated_attributes, clippy::duplicated_attributes)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: `-D clippy::duplicated-attributes` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::duplicated_attributes)]`
error: duplicated attribute
--> tests/ui/duplicated_attributes.rs:6:10
|
@ -14,8 +33,6 @@ help: remove this attribute
|
LL | #![allow(dead_code)]
| ^^^^^^^^^
= note: `-D clippy::duplicated-attributes` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::duplicated_attributes)]`
error: duplicated attribute
--> tests/ui/duplicated_attributes.rs:14:9
@ -34,5 +51,5 @@ help: remove this attribute
LL | #[allow(dead_code)]
| ^^^^^^^^^
error: aborting due to 2 previous errors
error: aborting due to 3 previous errors

View file

@ -1,6 +1,5 @@
//@aux-build: proc_macros.rs
#![warn(clippy::indexing_slicing)]
// We also check the out_of_bounds_indexing lint here, because it lints similar things and
// we want to avoid false positives.
#![warn(clippy::out_of_bounds_indexing)]

View file

@ -1,5 +1,5 @@
error: slicing may panic
--> tests/ui/indexing_slicing_slice.rs:115:6
--> tests/ui/indexing_slicing_slice.rs:114:6
|
LL | &x[index..];
| ^^^^^^^^^^
@ -9,7 +9,7 @@ LL | &x[index..];
= help: to override `-D warnings` add `#[allow(clippy::indexing_slicing)]`
error: slicing may panic
--> tests/ui/indexing_slicing_slice.rs:117:6
--> tests/ui/indexing_slicing_slice.rs:116:6
|
LL | &x[..index];
| ^^^^^^^^^^
@ -17,7 +17,7 @@ LL | &x[..index];
= help: consider using `.get(..n)`or `.get_mut(..n)` instead
error: slicing may panic
--> tests/ui/indexing_slicing_slice.rs:119:6
--> tests/ui/indexing_slicing_slice.rs:118:6
|
LL | &x[index_from..index_to];
| ^^^^^^^^^^^^^^^^^^^^^^^
@ -25,7 +25,7 @@ LL | &x[index_from..index_to];
= help: consider using `.get(n..m)` or `.get_mut(n..m)` instead
error: slicing may panic
--> tests/ui/indexing_slicing_slice.rs:121:6
--> tests/ui/indexing_slicing_slice.rs:120:6
|
LL | &x[index_from..][..index_to];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -33,7 +33,7 @@ LL | &x[index_from..][..index_to];
= help: consider using `.get(..n)`or `.get_mut(..n)` instead
error: slicing may panic
--> tests/ui/indexing_slicing_slice.rs:121:6
--> tests/ui/indexing_slicing_slice.rs:120:6
|
LL | &x[index_from..][..index_to];
| ^^^^^^^^^^^^^^^
@ -41,7 +41,7 @@ LL | &x[index_from..][..index_to];
= help: consider using `.get(n..)` or .get_mut(n..)` instead
error: slicing may panic
--> tests/ui/indexing_slicing_slice.rs:124:6
--> tests/ui/indexing_slicing_slice.rs:123:6
|
LL | &x[5..][..10];
| ^^^^^^^^^^^^
@ -49,7 +49,7 @@ LL | &x[5..][..10];
= help: consider using `.get(..n)`or `.get_mut(..n)` instead
error: range is out of bounds
--> tests/ui/indexing_slicing_slice.rs:124:8
--> tests/ui/indexing_slicing_slice.rs:123:8
|
LL | &x[5..][..10];
| ^
@ -58,7 +58,7 @@ LL | &x[5..][..10];
= help: to override `-D warnings` add `#[allow(clippy::out_of_bounds_indexing)]`
error: slicing may panic
--> tests/ui/indexing_slicing_slice.rs:127:6
--> tests/ui/indexing_slicing_slice.rs:126:6
|
LL | &x[0..][..3];
| ^^^^^^^^^^^
@ -66,7 +66,7 @@ LL | &x[0..][..3];
= help: consider using `.get(..n)`or `.get_mut(..n)` instead
error: slicing may panic
--> tests/ui/indexing_slicing_slice.rs:129:6
--> tests/ui/indexing_slicing_slice.rs:128:6
|
LL | &x[1..][..5];
| ^^^^^^^^^^^
@ -74,19 +74,19 @@ LL | &x[1..][..5];
= help: consider using `.get(..n)`or `.get_mut(..n)` instead
error: range is out of bounds
--> tests/ui/indexing_slicing_slice.rs:137:12
--> tests/ui/indexing_slicing_slice.rs:136:12
|
LL | &y[0..=4];
| ^
error: range is out of bounds
--> tests/ui/indexing_slicing_slice.rs:139:11
--> tests/ui/indexing_slicing_slice.rs:138:11
|
LL | &y[..=4];
| ^
error: slicing may panic
--> tests/ui/indexing_slicing_slice.rs:145:6
--> tests/ui/indexing_slicing_slice.rs:144:6
|
LL | &v[10..100];
| ^^^^^^^^^^
@ -94,7 +94,7 @@ LL | &v[10..100];
= help: consider using `.get(n..m)` or `.get_mut(n..m)` instead
error: slicing may panic
--> tests/ui/indexing_slicing_slice.rs:147:6
--> tests/ui/indexing_slicing_slice.rs:146:6
|
LL | &x[10..][..100];
| ^^^^^^^^^^^^^^
@ -102,13 +102,13 @@ LL | &x[10..][..100];
= help: consider using `.get(..n)`or `.get_mut(..n)` instead
error: range is out of bounds
--> tests/ui/indexing_slicing_slice.rs:147:8
--> tests/ui/indexing_slicing_slice.rs:146:8
|
LL | &x[10..][..100];
| ^^
error: slicing may panic
--> tests/ui/indexing_slicing_slice.rs:150:6
--> tests/ui/indexing_slicing_slice.rs:149:6
|
LL | &v[10..];
| ^^^^^^^
@ -116,7 +116,7 @@ LL | &v[10..];
= help: consider using `.get(n..)` or .get_mut(n..)` instead
error: slicing may panic
--> tests/ui/indexing_slicing_slice.rs:152:6
--> tests/ui/indexing_slicing_slice.rs:151:6
|
LL | &v[..100];
| ^^^^^^^^
@ -124,7 +124,7 @@ LL | &v[..100];
= help: consider using `.get(..n)`or `.get_mut(..n)` instead
error: indexing may panic
--> tests/ui/indexing_slicing_slice.rs:170:5
--> tests/ui/indexing_slicing_slice.rs:169:5
|
LL | map_with_get[true];
| ^^^^^^^^^^^^^^^^^^
@ -132,7 +132,7 @@ LL | map_with_get[true];
= help: consider using `.get(n)` or `.get_mut(n)` instead
error: indexing may panic
--> tests/ui/indexing_slicing_slice.rs:174:5
--> tests/ui/indexing_slicing_slice.rs:173:5
|
LL | s[0];
| ^^^^
@ -140,7 +140,7 @@ LL | s[0];
= help: consider using `.get(n)` or `.get_mut(n)` instead
error: indexing may panic
--> tests/ui/indexing_slicing_slice.rs:178:5
--> tests/ui/indexing_slicing_slice.rs:177:5
|
LL | y[0];
| ^^^^

View file

@ -1,5 +1,4 @@
#![allow(clippy::uninlined_format_args, clippy::useless_vec)]
#![allow(clippy::needless_if, clippy::uninlined_format_args)]
#![allow(clippy::uninlined_format_args, clippy::useless_vec, clippy::needless_if)]
#![warn(clippy::needless_collect)]
//@no-rustfix
use std::collections::{BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};

View file

@ -1,5 +1,5 @@
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:9:39
--> tests/ui/needless_collect_indirect.rs:8:39
|
LL | let indirect_iter = sample.iter().collect::<Vec<_>>();
| ^^^^^^^
@ -18,7 +18,7 @@ LL ~ sample.iter().map(|x| (x, x + 1)).collect::<HashMap<_, _>>();
|
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:13:38
--> tests/ui/needless_collect_indirect.rs:12:38
|
LL | let indirect_len = sample.iter().collect::<VecDeque<_>>();
| ^^^^^^^
@ -35,7 +35,7 @@ LL ~ sample.iter().count();
|
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:17:40
--> tests/ui/needless_collect_indirect.rs:16:40
|
LL | let indirect_empty = sample.iter().collect::<VecDeque<_>>();
| ^^^^^^^
@ -52,7 +52,7 @@ LL ~ sample.iter().next().is_none();
|
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:21:43
--> tests/ui/needless_collect_indirect.rs:20:43
|
LL | let indirect_contains = sample.iter().collect::<VecDeque<_>>();
| ^^^^^^^
@ -69,7 +69,7 @@ LL ~ sample.iter().any(|x| x == &5);
|
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:35:48
--> tests/ui/needless_collect_indirect.rs:34:48
|
LL | let non_copy_contains = sample.into_iter().collect::<Vec<_>>();
| ^^^^^^^
@ -86,7 +86,7 @@ LL ~ sample.into_iter().any(|x| x == a);
|
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:66:51
--> tests/ui/needless_collect_indirect.rs:65:51
|
LL | let buffer: Vec<&str> = string.split('/').collect();
| ^^^^^^^
@ -103,7 +103,7 @@ LL ~ string.split('/').count()
|
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:73:55
--> tests/ui/needless_collect_indirect.rs:72:55
|
LL | let indirect_len: VecDeque<_> = sample.iter().collect();
| ^^^^^^^
@ -120,7 +120,7 @@ LL ~ sample.iter().count()
|
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:80:57
--> tests/ui/needless_collect_indirect.rs:79:57
|
LL | let indirect_len: LinkedList<_> = sample.iter().collect();
| ^^^^^^^
@ -137,7 +137,7 @@ LL ~ sample.iter().count()
|
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:87:57
--> tests/ui/needless_collect_indirect.rs:86:57
|
LL | let indirect_len: BinaryHeap<_> = sample.iter().collect();
| ^^^^^^^
@ -154,7 +154,7 @@ LL ~ sample.iter().count()
|
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:149:59
--> tests/ui/needless_collect_indirect.rs:148:59
|
LL | let y: Vec<usize> = vec.iter().map(|k| k * k).collect();
| ^^^^^^^
@ -172,7 +172,7 @@ LL ~ vec.iter().map(|k| k * k).any(|x| x == i);
|
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:176:59
--> tests/ui/needless_collect_indirect.rs:175:59
|
LL | let y: Vec<usize> = vec.iter().map(|k| k * k).collect();
| ^^^^^^^
@ -190,7 +190,7 @@ LL ~ vec.iter().map(|k| k * k).any(|x| x == n);
|
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:207:63
--> tests/ui/needless_collect_indirect.rs:206:63
|
LL | let y: Vec<usize> = vec.iter().map(|k| k * k).collect();
| ^^^^^^^
@ -208,7 +208,7 @@ LL ~ vec.iter().map(|k| k * k).any(|x| x == n);
|
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:245:59
--> tests/ui/needless_collect_indirect.rs:244:59
|
LL | let y: Vec<usize> = vec.iter().map(|k| k * k).collect();
| ^^^^^^^
@ -226,7 +226,7 @@ LL ~ vec.iter().map(|k| k * k).any(|x| x == n);
|
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:272:26
--> tests/ui/needless_collect_indirect.rs:271:26
|
LL | let w = v.iter().collect::<Vec<_>>();
| ^^^^^^^
@ -244,7 +244,7 @@ LL ~ for _ in 0..v.iter().count() {
|
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:296:30
--> tests/ui/needless_collect_indirect.rs:295:30
|
LL | let mut w = v.iter().collect::<Vec<_>>();
| ^^^^^^^
@ -262,7 +262,7 @@ LL ~ while 1 == v.iter().count() {
|
error: avoid using `collect()` when not needed
--> tests/ui/needless_collect_indirect.rs:320:30
--> tests/ui/needless_collect_indirect.rs:319:30
|
LL | let mut w = v.iter().collect::<Vec<_>>();
| ^^^^^^^

View file

@ -1,5 +1,6 @@
//@no-rustfix
#![allow(clippy::duplicated_attributes)]
#![warn(clippy::unnecessary_clippy_cfg)]
#![cfg_attr(clippy, deny(clippy::non_minimal_cfg))]
//~^ unnecessary_clippy_cfg
@ -7,7 +8,6 @@
//~^ unnecessary_clippy_cfg
#![cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))]
//~^ unnecessary_clippy_cfg
//~| duplicated_attributes
#![cfg_attr(clippy, deny(clippy::non_minimal_cfg))]
//~^ unnecessary_clippy_cfg
@ -17,7 +17,6 @@
//~^ unnecessary_clippy_cfg
#[cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))]
//~^ unnecessary_clippy_cfg
//~| duplicated_attributes
#[cfg_attr(clippy, deny(clippy::non_minimal_cfg))]
//~^ unnecessary_clippy_cfg

View file

@ -1,5 +1,5 @@
error: no need to put clippy lints behind a `clippy` cfg
--> tests/ui/unnecessary_clippy_cfg.rs:4:1
--> tests/ui/unnecessary_clippy_cfg.rs:5:1
|
LL | #![cfg_attr(clippy, deny(clippy::non_minimal_cfg))]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `#![deny(clippy::non_minimal_cfg)]`
@ -8,7 +8,7 @@ LL | #![cfg_attr(clippy, deny(clippy::non_minimal_cfg))]
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_clippy_cfg)]`
error: no need to put clippy lints behind a `clippy` cfg
--> tests/ui/unnecessary_clippy_cfg.rs:6:37
--> tests/ui/unnecessary_clippy_cfg.rs:7:37
|
LL | #![cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))]
| ^^^^^^^^^^^^^^^^^^^^^^^
@ -16,7 +16,7 @@ LL | #![cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))]
= note: write instead: `#![deny(clippy::non_minimal_cfg)]`
error: no need to put clippy lints behind a `clippy` cfg
--> tests/ui/unnecessary_clippy_cfg.rs:8:37
--> tests/ui/unnecessary_clippy_cfg.rs:9:37
|
LL | #![cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))]
| ^^^^^^^^^^^^^^^^^^^^^^^
@ -52,46 +52,10 @@ LL | #[cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))]
= note: write instead: `#[deny(clippy::non_minimal_cfg)]`
error: no need to put clippy lints behind a `clippy` cfg
--> tests/ui/unnecessary_clippy_cfg.rs:21:1
--> tests/ui/unnecessary_clippy_cfg.rs:20:1
|
LL | #[cfg_attr(clippy, deny(clippy::non_minimal_cfg))]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `#[deny(clippy::non_minimal_cfg)]`
error: duplicated attribute
--> tests/ui/unnecessary_clippy_cfg.rs:8:26
|
LL | #![cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))]
| ^^^^^^^^^
|
note: first defined here
--> tests/ui/unnecessary_clippy_cfg.rs:6:26
|
LL | #![cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))]
| ^^^^^^^^^
help: remove this attribute
--> tests/ui/unnecessary_clippy_cfg.rs:8:26
|
LL | #![cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))]
| ^^^^^^^^^
= note: `-D clippy::duplicated-attributes` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::duplicated_attributes)]`
error: duplicated attribute
--> tests/ui/unnecessary_clippy_cfg.rs:18:25
|
LL | #[cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))]
| ^^^^^^^^^
|
note: first defined here
--> tests/ui/unnecessary_clippy_cfg.rs:16:25
|
LL | #[cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))]
| ^^^^^^^^^
help: remove this attribute
--> tests/ui/unnecessary_clippy_cfg.rs:18:25
|
LL | #[cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))]
| ^^^^^^^^^
error: aborting due to 10 previous errors
error: aborting due to 8 previous errors