]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/trait_bounds.rs
Auto merge of #8249 - sourcefrog:depinfo, r=Manishearth
[rust.git] / clippy_lints / src / trait_bounds.rs
1 use clippy_utils::diagnostics::span_lint_and_help;
2 use clippy_utils::source::{snippet, snippet_with_applicability};
3 use clippy_utils::{SpanlessEq, SpanlessHash};
4 use core::hash::{Hash, Hasher};
5 use if_chain::if_chain;
6 use rustc_data_structures::fx::FxHashMap;
7 use rustc_data_structures::unhash::UnhashMap;
8 use rustc_errors::Applicability;
9 use rustc_hir::{def::Res, GenericBound, Generics, ParamName, Path, QPath, Ty, TyKind, WherePredicate};
10 use rustc_lint::{LateContext, LateLintPass};
11 use rustc_session::{declare_tool_lint, impl_lint_pass};
12 use rustc_span::Span;
13
14 declare_clippy_lint! {
15     /// ### What it does
16     /// This lint warns about unnecessary type repetitions in trait bounds
17     ///
18     /// ### Why is this bad?
19     /// Repeating the type for every bound makes the code
20     /// less readable than combining the bounds
21     ///
22     /// ### Example
23     /// ```rust
24     /// pub fn foo<T>(t: T) where T: Copy, T: Clone {}
25     /// ```
26     ///
27     /// Could be written as:
28     ///
29     /// ```rust
30     /// pub fn foo<T>(t: T) where T: Copy + Clone {}
31     /// ```
32     #[clippy::version = "1.38.0"]
33     pub TYPE_REPETITION_IN_BOUNDS,
34     pedantic,
35     "Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`"
36 }
37
38 declare_clippy_lint! {
39     /// ### What it does
40     /// Checks for cases where generics are being used and multiple
41     /// syntax specifications for trait bounds are used simultaneously.
42     ///
43     /// ### Why is this bad?
44     /// Duplicate bounds makes the code
45     /// less readable than specifing them only once.
46     ///
47     /// ### Example
48     /// ```rust
49     /// fn func<T: Clone + Default>(arg: T) where T: Clone + Default {}
50     /// ```
51     ///
52     /// Could be written as:
53     ///
54     /// ```rust
55     /// fn func<T: Clone + Default>(arg: T) {}
56     /// ```
57     /// or
58     ///
59     /// ```rust
60     /// fn func<T>(arg: T) where T: Clone + Default {}
61     /// ```
62     #[clippy::version = "1.47.0"]
63     pub TRAIT_DUPLICATION_IN_BOUNDS,
64     pedantic,
65     "Check if the same trait bounds are specified twice during a function declaration"
66 }
67
68 #[derive(Copy, Clone)]
69 pub struct TraitBounds {
70     max_trait_bounds: u64,
71 }
72
73 impl TraitBounds {
74     #[must_use]
75     pub fn new(max_trait_bounds: u64) -> Self {
76         Self { max_trait_bounds }
77     }
78 }
79
80 impl_lint_pass!(TraitBounds => [TYPE_REPETITION_IN_BOUNDS, TRAIT_DUPLICATION_IN_BOUNDS]);
81
82 impl<'tcx> LateLintPass<'tcx> for TraitBounds {
83     fn check_generics(&mut self, cx: &LateContext<'tcx>, gen: &'tcx Generics<'_>) {
84         self.check_type_repetition(cx, gen);
85         check_trait_bound_duplication(cx, gen);
86     }
87 }
88
89 fn get_trait_res_span_from_bound(bound: &GenericBound<'_>) -> Option<(Res, Span)> {
90     if let GenericBound::Trait(t, _) = bound {
91         Some((t.trait_ref.path.res, t.span))
92     } else {
93         None
94     }
95 }
96
97 impl TraitBounds {
98     fn check_type_repetition<'tcx>(self, cx: &LateContext<'tcx>, gen: &'tcx Generics<'_>) {
99         struct SpanlessTy<'cx, 'tcx> {
100             ty: &'tcx Ty<'tcx>,
101             cx: &'cx LateContext<'tcx>,
102         }
103         impl PartialEq for SpanlessTy<'_, '_> {
104             fn eq(&self, other: &Self) -> bool {
105                 let mut eq = SpanlessEq::new(self.cx);
106                 eq.inter_expr().eq_ty(self.ty, other.ty)
107             }
108         }
109         impl Hash for SpanlessTy<'_, '_> {
110             fn hash<H: Hasher>(&self, h: &mut H) {
111                 let mut t = SpanlessHash::new(self.cx);
112                 t.hash_ty(self.ty);
113                 h.write_u64(t.finish());
114             }
115         }
116         impl Eq for SpanlessTy<'_, '_> {}
117
118         if gen.span.from_expansion() {
119             return;
120         }
121         let mut map: UnhashMap<SpanlessTy<'_, '_>, Vec<&GenericBound<'_>>> = UnhashMap::default();
122         let mut applicability = Applicability::MaybeIncorrect;
123         for bound in gen.where_clause.predicates {
124             if_chain! {
125                 if let WherePredicate::BoundPredicate(ref p) = bound;
126                 if p.bounds.len() as u64 <= self.max_trait_bounds;
127                 if !p.span.from_expansion();
128                 if let Some(ref v) = map.insert(
129                     SpanlessTy { ty: p.bounded_ty, cx },
130                     p.bounds.iter().collect::<Vec<_>>()
131                 );
132
133                 then {
134                     let mut hint_string = format!(
135                         "consider combining the bounds: `{}:",
136                         snippet(cx, p.bounded_ty.span, "_")
137                     );
138                     for b in v.iter() {
139                         if let GenericBound::Trait(ref poly_trait_ref, _) = b {
140                             let path = &poly_trait_ref.trait_ref.path;
141                             hint_string.push_str(&format!(
142                                 " {} +",
143                                 snippet_with_applicability(cx, path.span, "..", &mut applicability)
144                             ));
145                         }
146                     }
147                     for b in p.bounds.iter() {
148                         if let GenericBound::Trait(ref poly_trait_ref, _) = b {
149                             let path = &poly_trait_ref.trait_ref.path;
150                             hint_string.push_str(&format!(
151                                 " {} +",
152                                 snippet_with_applicability(cx, path.span, "..", &mut applicability)
153                             ));
154                         }
155                     }
156                     hint_string.truncate(hint_string.len() - 2);
157                     hint_string.push('`');
158                     span_lint_and_help(
159                         cx,
160                         TYPE_REPETITION_IN_BOUNDS,
161                         p.span,
162                         "this type has already been used as a bound predicate",
163                         None,
164                         &hint_string,
165                     );
166                 }
167             }
168         }
169     }
170 }
171
172 fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
173     if gen.span.from_expansion() || gen.params.is_empty() || gen.where_clause.predicates.is_empty() {
174         return;
175     }
176
177     let mut map = FxHashMap::default();
178     for param in gen.params {
179         if let ParamName::Plain(ref ident) = param.name {
180             let res = param
181                 .bounds
182                 .iter()
183                 .filter_map(get_trait_res_span_from_bound)
184                 .collect::<Vec<_>>();
185             map.insert(*ident, res);
186         }
187     }
188
189     for predicate in gen.where_clause.predicates {
190         if_chain! {
191             if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate;
192             if !bound_predicate.span.from_expansion();
193             if let TyKind::Path(QPath::Resolved(_, Path { segments, .. })) = bound_predicate.bounded_ty.kind;
194             if let Some(segment) = segments.first();
195             if let Some(trait_resolutions_direct) = map.get(&segment.ident);
196             then {
197                 for (res_where, _) in bound_predicate.bounds.iter().filter_map(get_trait_res_span_from_bound) {
198                     if let Some((_, span_direct)) = trait_resolutions_direct
199                                                 .iter()
200                                                 .find(|(res_direct, _)| *res_direct == res_where) {
201                         span_lint_and_help(
202                             cx,
203                             TRAIT_DUPLICATION_IN_BOUNDS,
204                             *span_direct,
205                             "this trait bound is already specified in the where clause",
206                             None,
207                             "consider removing this trait bound",
208                         );
209                     }
210                 }
211             }
212         }
213     }
214 }