Skip to content

Commit 188e4a8

Browse files
Allen Hsualdhsu
Allen Hsu
authored andcommitted
Lint for repeated repeated traits within trait bounds or where clauses.
1 parent 526f02e commit 188e4a8

File tree

4 files changed

+340
-15
lines changed

4 files changed

+340
-15
lines changed

clippy_lints/src/trait_bounds.rs

+123-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use clippy_utils::diagnostics::span_lint_and_help;
2-
use clippy_utils::source::{snippet, snippet_with_applicability};
1+
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
2+
use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
33
use clippy_utils::{SpanlessEq, SpanlessHash};
44
use core::hash::{Hash, Hasher};
55
use if_chain::if_chain;
@@ -9,8 +9,8 @@ use rustc_data_structures::unhash::UnhashMap;
99
use rustc_errors::Applicability;
1010
use rustc_hir::def::Res;
1111
use rustc_hir::{
12-
GenericBound, Generics, Item, ItemKind, Node, Path, PathSegment, PredicateOrigin, QPath, TraitBoundModifier,
13-
TraitItem, Ty, TyKind, WherePredicate,
12+
GenericArg, GenericBound, Generics, Item, ItemKind, Node, Path, PathSegment, PredicateOrigin, QPath,
13+
TraitBoundModifier, TraitItem, TraitRef, Ty, TyKind, WherePredicate,
1414
};
1515
use rustc_lint::{LateContext, LateLintPass};
1616
use rustc_session::{declare_tool_lint, impl_lint_pass};
@@ -36,7 +36,7 @@ declare_clippy_lint! {
3636
#[clippy::version = "1.38.0"]
3737
pub TYPE_REPETITION_IN_BOUNDS,
3838
nursery,
39-
"Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`"
39+
"types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`"
4040
}
4141

4242
declare_clippy_lint! {
@@ -63,10 +63,26 @@ declare_clippy_lint! {
6363
///
6464
/// fn func<T>(arg: T) where T: Clone + Default {}
6565
/// ```
66+
///
67+
/// ```rust
68+
/// fn foo<T: Default + Default>(bar: T) {}
69+
/// ```
70+
/// Use instead:
71+
/// ```rust
72+
/// fn foo<T: Default>(bar: T) {}
73+
/// ```
74+
///
75+
/// ```rust
76+
/// fn foo<T>(bar: T) where T: Default + Default {}
77+
/// ```
78+
/// Use instead:
79+
/// ```rust
80+
/// fn foo<T>(bar: T) where T: Default {}
81+
/// ```
6682
#[clippy::version = "1.47.0"]
6783
pub TRAIT_DUPLICATION_IN_BOUNDS,
6884
nursery,
69-
"Check if the same trait bounds are specified twice during a function declaration"
85+
"check if the same trait bounds are specified more than once during a generic declaration"
7086
}
7187

7288
#[derive(Copy, Clone)]
@@ -87,6 +103,19 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds {
87103
fn check_generics(&mut self, cx: &LateContext<'tcx>, gen: &'tcx Generics<'_>) {
88104
self.check_type_repetition(cx, gen);
89105
check_trait_bound_duplication(cx, gen);
106+
check_bounds_or_where_duplication(cx, gen);
107+
}
108+
109+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
110+
// special handling for self trait bounds as these are not considered generics
111+
// ie. trait Foo: Display {}
112+
if let Item {
113+
kind: ItemKind::Trait(_, _, _, bounds, ..),
114+
..
115+
} = item
116+
{
117+
rollup_traits(cx, bounds, "these bounds contain repeated elements");
118+
}
90119
}
91120

92121
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'tcx>) {
@@ -241,6 +270,26 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
241270
}
242271
}
243272

273+
#[derive(PartialEq, Eq, Hash, Debug)]
274+
struct ComparableTraitRef(Res, Vec<Res>);
275+
276+
fn check_bounds_or_where_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
277+
if gen.span.from_expansion() {
278+
return;
279+
}
280+
281+
for predicate in gen.predicates {
282+
if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate {
283+
let msg = if predicate.in_where_clause() {
284+
"these where clauses contain repeated elements"
285+
} else {
286+
"these bounds contain repeated elements"
287+
};
288+
rollup_traits(cx, bound_predicate.bounds, msg);
289+
}
290+
}
291+
}
292+
244293
fn get_trait_info_from_bound<'a>(bound: &'a GenericBound<'_>) -> Option<(Res, &'a [PathSegment<'a>], Span)> {
245294
if let GenericBound::Trait(t, tbm) = bound {
246295
let trait_path = t.trait_ref.path;
@@ -257,3 +306,71 @@ fn get_trait_info_from_bound<'a>(bound: &'a GenericBound<'_>) -> Option<(Res, &'
257306
None
258307
}
259308
}
309+
310+
// FIXME: ComparableTraitRef does not support nested bounds needed for associated_type_bounds
311+
fn into_comparable_trait_ref(trait_ref: &TraitRef<'_>) -> ComparableTraitRef {
312+
ComparableTraitRef(
313+
trait_ref.path.res,
314+
trait_ref
315+
.path
316+
.segments
317+
.iter()
318+
.filter_map(|segment| {
319+
// get trait bound type arguments
320+
Some(segment.args?.args.iter().filter_map(|arg| {
321+
if_chain! {
322+
if let GenericArg::Type(ty) = arg;
323+
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
324+
then { return Some(path.res) }
325+
}
326+
None
327+
}))
328+
})
329+
.flatten()
330+
.collect(),
331+
)
332+
}
333+
334+
fn rollup_traits(cx: &LateContext<'_>, bounds: &[GenericBound<'_>], msg: &str) {
335+
let mut map = FxHashMap::default();
336+
let mut repeated_res = false;
337+
338+
let only_comparable_trait_refs = |bound: &GenericBound<'_>| {
339+
if let GenericBound::Trait(t, _) = bound {
340+
Some((into_comparable_trait_ref(&t.trait_ref), t.span))
341+
} else {
342+
None
343+
}
344+
};
345+
346+
for bound in bounds.iter().filter_map(only_comparable_trait_refs) {
347+
let (comparable_bound, span_direct) = bound;
348+
if map.insert(comparable_bound, span_direct).is_some() {
349+
repeated_res = true;
350+
}
351+
}
352+
353+
if_chain! {
354+
if repeated_res;
355+
if let [first_trait, .., last_trait] = bounds;
356+
then {
357+
let all_trait_span = first_trait.span().to(last_trait.span());
358+
359+
let mut traits = map.values()
360+
.filter_map(|span| snippet_opt(cx, *span))
361+
.collect::<Vec<_>>();
362+
traits.sort_unstable();
363+
let traits = traits.join(" + ");
364+
365+
span_lint_and_sugg(
366+
cx,
367+
TRAIT_DUPLICATION_IN_BOUNDS,
368+
all_trait_span,
369+
msg,
370+
"try",
371+
traits,
372+
Applicability::MachineApplicable
373+
);
374+
}
375+
}
376+
}

tests/compile-test.rs

+1
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ const RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS: &[&str] = &[
397397
"single_component_path_imports_nested_first.rs",
398398
"string_add.rs",
399399
"toplevel_ref_arg_non_rustfix.rs",
400+
"trait_duplication_in_bounds.rs",
400401
"unit_arg.rs",
401402
"unnecessary_clone.rs",
402403
"unnecessary_lazy_eval_unfixable.rs",

tests/ui/trait_duplication_in_bounds.rs

+111
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![deny(clippy::trait_duplication_in_bounds)]
2+
#![allow(unused)]
23

34
use std::collections::BTreeMap;
45
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
@@ -98,4 +99,114 @@ trait FooIter: Iterator<Item = Foo> {
9899
// This should not lint
99100
fn impl_trait(_: impl AsRef<str>, _: impl AsRef<str>) {}
100101

102+
mod repeated_where_clauses_or_trait_bounds {
103+
fn bad_foo<T: Clone + Clone + Clone + Copy, U: Clone + Copy>(arg0: T, argo1: U) {
104+
unimplemented!();
105+
}
106+
107+
fn bad_bar<T, U>(arg0: T, arg1: U)
108+
where
109+
T: Clone + Clone + Clone + Copy,
110+
U: Clone + Copy,
111+
{
112+
unimplemented!();
113+
}
114+
115+
fn good_bar<T: Clone + Copy, U: Clone + Copy>(arg0: T, arg1: U) {
116+
unimplemented!();
117+
}
118+
119+
fn good_foo<T, U>(arg0: T, arg1: U)
120+
where
121+
T: Clone + Copy,
122+
U: Clone + Copy,
123+
{
124+
unimplemented!();
125+
}
126+
127+
trait GoodSelfTraitBound: Clone + Copy {
128+
fn f();
129+
}
130+
131+
trait GoodSelfWhereClause {
132+
fn f()
133+
where
134+
Self: Clone + Copy;
135+
}
136+
137+
trait BadSelfTraitBound: Clone + Clone + Clone {
138+
fn f();
139+
}
140+
141+
trait BadSelfWhereClause {
142+
fn f()
143+
where
144+
Self: Clone + Clone + Clone;
145+
}
146+
147+
trait GoodTraitBound<T: Clone + Copy, U: Clone + Copy> {
148+
fn f();
149+
}
150+
151+
trait GoodWhereClause<T, U> {
152+
fn f()
153+
where
154+
T: Clone + Copy,
155+
U: Clone + Copy;
156+
}
157+
158+
trait BadTraitBound<T: Clone + Clone + Clone + Copy, U: Clone + Copy> {
159+
fn f();
160+
}
161+
162+
trait BadWhereClause<T, U> {
163+
fn f()
164+
where
165+
T: Clone + Clone + Clone + Copy,
166+
U: Clone + Copy;
167+
}
168+
169+
struct GoodStructBound<T: Clone + Copy, U: Clone + Copy> {
170+
t: T,
171+
u: U,
172+
}
173+
174+
impl<T: Clone + Copy, U: Clone + Copy> GoodTraitBound<T, U> for GoodStructBound<T, U> {
175+
// this should not warn
176+
fn f() {}
177+
}
178+
179+
struct GoodStructWhereClause;
180+
181+
impl<T, U> GoodTraitBound<T, U> for GoodStructWhereClause
182+
where
183+
T: Clone + Copy,
184+
U: Clone + Copy,
185+
{
186+
// this should not warn
187+
fn f() {}
188+
}
189+
190+
fn no_error_separate_arg_bounds(program: impl AsRef<()>, dir: impl AsRef<()>, args: &[impl AsRef<()>]) {}
191+
192+
trait GenericTrait<T> {}
193+
194+
// This should not warn but currently does see #8757
195+
fn good_generic<T: GenericTrait<u64> + GenericTrait<u32>>(arg0: T) {
196+
unimplemented!();
197+
}
198+
199+
fn bad_generic<T: GenericTrait<u64> + GenericTrait<u32> + GenericTrait<u64>>(arg0: T) {
200+
unimplemented!();
201+
}
202+
203+
mod foo {
204+
pub trait Clone {}
205+
}
206+
207+
fn qualified_path<T: std::clone::Clone + Clone + foo::Clone>(arg0: T) {
208+
unimplemented!();
209+
}
210+
}
211+
101212
fn main() {}

0 commit comments

Comments
 (0)