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