]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | use crate::utils::sugg::Sugg; |
2 | use crate::utils::{ | |
3 | differing_macro_contexts, eq_expr_value, is_type_diagnostic_item, snippet_with_applicability, span_lint_and_then, | |
4 | }; | |
5 | use if_chain::if_chain; | |
6 | use rustc_errors::Applicability; | |
7 | use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, StmtKind}; | |
8 | use rustc_lint::{LateContext, LateLintPass}; | |
9 | use rustc_middle::ty; | |
10 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
11 | use rustc_span::sym; | |
12 | ||
13 | declare_clippy_lint! { | |
14 | /// **What it does:** Checks for manual swapping. | |
15 | /// | |
16 | /// **Why is this bad?** The `std::mem::swap` function exposes the intent better | |
17 | /// without deinitializing or copying either variable. | |
18 | /// | |
19 | /// **Known problems:** None. | |
20 | /// | |
21 | /// **Example:** | |
22 | /// ```rust | |
23 | /// let mut a = 42; | |
24 | /// let mut b = 1337; | |
25 | /// | |
26 | /// let t = b; | |
27 | /// b = a; | |
28 | /// a = t; | |
29 | /// ``` | |
30 | /// Use std::mem::swap(): | |
31 | /// ```rust | |
32 | /// let mut a = 1; | |
33 | /// let mut b = 2; | |
34 | /// std::mem::swap(&mut a, &mut b); | |
35 | /// ``` | |
36 | pub MANUAL_SWAP, | |
37 | complexity, | |
38 | "manual swap of two variables" | |
39 | } | |
40 | ||
41 | declare_clippy_lint! { | |
42 | /// **What it does:** Checks for `foo = bar; bar = foo` sequences. | |
43 | /// | |
44 | /// **Why is this bad?** This looks like a failed attempt to swap. | |
45 | /// | |
46 | /// **Known problems:** None. | |
47 | /// | |
48 | /// **Example:** | |
49 | /// ```rust | |
50 | /// # let mut a = 1; | |
51 | /// # let mut b = 2; | |
52 | /// a = b; | |
53 | /// b = a; | |
54 | /// ``` | |
55 | /// If swapping is intended, use `swap()` instead: | |
56 | /// ```rust | |
57 | /// # let mut a = 1; | |
58 | /// # let mut b = 2; | |
59 | /// std::mem::swap(&mut a, &mut b); | |
60 | /// ``` | |
61 | pub ALMOST_SWAPPED, | |
62 | correctness, | |
63 | "`foo = bar; bar = foo` sequence" | |
64 | } | |
65 | ||
66 | declare_lint_pass!(Swap => [MANUAL_SWAP, ALMOST_SWAPPED]); | |
67 | ||
68 | impl<'tcx> LateLintPass<'tcx> for Swap { | |
69 | fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { | |
70 | check_manual_swap(cx, block); | |
71 | check_suspicious_swap(cx, block); | |
72 | } | |
73 | } | |
74 | ||
75 | /// Implementation of the `MANUAL_SWAP` lint. | |
76 | fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) { | |
77 | for w in block.stmts.windows(3) { | |
78 | if_chain! { | |
79 | // let t = foo(); | |
80 | if let StmtKind::Local(ref tmp) = w[0].kind; | |
81 | if let Some(ref tmp_init) = tmp.init; | |
82 | if let PatKind::Binding(.., ident, None) = tmp.pat.kind; | |
83 | ||
84 | // foo() = bar(); | |
85 | if let StmtKind::Semi(ref first) = w[1].kind; | |
86 | if let ExprKind::Assign(ref lhs1, ref rhs1, _) = first.kind; | |
87 | ||
88 | // bar() = t; | |
89 | if let StmtKind::Semi(ref second) = w[2].kind; | |
90 | if let ExprKind::Assign(ref lhs2, ref rhs2, _) = second.kind; | |
91 | if let ExprKind::Path(QPath::Resolved(None, ref rhs2)) = rhs2.kind; | |
92 | if rhs2.segments.len() == 1; | |
93 | ||
94 | if ident.name == rhs2.segments[0].ident.name; | |
95 | if eq_expr_value(cx, tmp_init, lhs1); | |
96 | if eq_expr_value(cx, rhs1, lhs2); | |
97 | then { | |
98 | if let ExprKind::Field(ref lhs1, _) = lhs1.kind { | |
99 | if let ExprKind::Field(ref lhs2, _) = lhs2.kind { | |
100 | if lhs1.hir_id.owner == lhs2.hir_id.owner { | |
101 | return; | |
102 | } | |
103 | } | |
104 | } | |
105 | ||
106 | let mut applicability = Applicability::MachineApplicable; | |
107 | ||
108 | let slice = check_for_slice(cx, lhs1, lhs2); | |
109 | let (replace, what, sugg) = if let Slice::NotSwappable = slice { | |
110 | return; | |
111 | } else if let Slice::Swappable(slice, idx1, idx2) = slice { | |
112 | if let Some(slice) = Sugg::hir_opt(cx, slice) { | |
113 | ( | |
114 | false, | |
115 | format!(" elements of `{}`", slice), | |
116 | format!( | |
117 | "{}.swap({}, {})", | |
118 | slice.maybe_par(), | |
119 | snippet_with_applicability(cx, idx1.span, "..", &mut applicability), | |
120 | snippet_with_applicability(cx, idx2.span, "..", &mut applicability), | |
121 | ), | |
122 | ) | |
123 | } else { | |
124 | (false, String::new(), String::new()) | |
125 | } | |
126 | } else if let (Some(first), Some(second)) = (Sugg::hir_opt(cx, lhs1), Sugg::hir_opt(cx, rhs1)) { | |
127 | ( | |
128 | true, | |
129 | format!(" `{}` and `{}`", first, second), | |
130 | format!("std::mem::swap({}, {})", first.mut_addr(), second.mut_addr()), | |
131 | ) | |
132 | } else { | |
133 | (true, String::new(), String::new()) | |
134 | }; | |
135 | ||
136 | let span = w[0].span.to(second.span); | |
137 | ||
138 | span_lint_and_then( | |
139 | cx, | |
140 | MANUAL_SWAP, | |
141 | span, | |
142 | &format!("this looks like you are swapping{} manually", what), | |
143 | |diag| { | |
144 | if !sugg.is_empty() { | |
145 | diag.span_suggestion( | |
146 | span, | |
147 | "try", | |
148 | sugg, | |
149 | applicability, | |
150 | ); | |
151 | ||
152 | if replace { | |
153 | diag.note("or maybe you should use `std::mem::replace`?"); | |
154 | } | |
155 | } | |
156 | } | |
157 | ); | |
158 | } | |
159 | } | |
160 | } | |
161 | } | |
162 | ||
163 | enum Slice<'a> { | |
164 | /// `slice.swap(idx1, idx2)` can be used | |
165 | /// | |
166 | /// ## Example | |
167 | /// | |
168 | /// ```rust | |
169 | /// # let mut a = vec![0, 1]; | |
170 | /// let t = a[1]; | |
171 | /// a[1] = a[0]; | |
172 | /// a[0] = t; | |
173 | /// // can be written as | |
174 | /// a.swap(0, 1); | |
175 | /// ``` | |
176 | Swappable(&'a Expr<'a>, &'a Expr<'a>, &'a Expr<'a>), | |
177 | /// The `swap` function cannot be used. | |
178 | /// | |
179 | /// ## Example | |
180 | /// | |
181 | /// ```rust | |
182 | /// # let mut a = [vec![1, 2], vec![3, 4]]; | |
183 | /// let t = a[0][1]; | |
184 | /// a[0][1] = a[1][0]; | |
185 | /// a[1][0] = t; | |
186 | /// ``` | |
187 | NotSwappable, | |
188 | /// Not a slice | |
189 | None, | |
190 | } | |
191 | ||
192 | /// Checks if both expressions are index operations into "slice-like" types. | |
193 | fn check_for_slice<'a>(cx: &LateContext<'_>, lhs1: &'a Expr<'_>, lhs2: &'a Expr<'_>) -> Slice<'a> { | |
194 | if let ExprKind::Index(ref lhs1, ref idx1) = lhs1.kind { | |
195 | if let ExprKind::Index(ref lhs2, ref idx2) = lhs2.kind { | |
196 | if eq_expr_value(cx, lhs1, lhs2) { | |
197 | let ty = cx.typeck_results().expr_ty(lhs1).peel_refs(); | |
198 | ||
199 | if matches!(ty.kind(), ty::Slice(_)) | |
200 | || matches!(ty.kind(), ty::Array(_, _)) | |
201 | || is_type_diagnostic_item(cx, ty, sym::vec_type) | |
202 | || is_type_diagnostic_item(cx, ty, sym::vecdeque_type) | |
203 | { | |
204 | return Slice::Swappable(lhs1, idx1, idx2); | |
205 | } | |
206 | } else { | |
207 | return Slice::NotSwappable; | |
208 | } | |
209 | } | |
210 | } | |
211 | ||
212 | Slice::None | |
213 | } | |
214 | ||
215 | /// Implementation of the `ALMOST_SWAPPED` lint. | |
216 | fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) { | |
217 | for w in block.stmts.windows(2) { | |
218 | if_chain! { | |
219 | if let StmtKind::Semi(ref first) = w[0].kind; | |
220 | if let StmtKind::Semi(ref second) = w[1].kind; | |
221 | if !differing_macro_contexts(first.span, second.span); | |
222 | if let ExprKind::Assign(ref lhs0, ref rhs0, _) = first.kind; | |
223 | if let ExprKind::Assign(ref lhs1, ref rhs1, _) = second.kind; | |
224 | if eq_expr_value(cx, lhs0, rhs1); | |
225 | if eq_expr_value(cx, lhs1, rhs0); | |
226 | then { | |
227 | let lhs0 = Sugg::hir_opt(cx, lhs0); | |
228 | let rhs0 = Sugg::hir_opt(cx, rhs0); | |
229 | let (what, lhs, rhs) = if let (Some(first), Some(second)) = (lhs0, rhs0) { | |
230 | ( | |
231 | format!(" `{}` and `{}`", first, second), | |
232 | first.mut_addr().to_string(), | |
233 | second.mut_addr().to_string(), | |
234 | ) | |
235 | } else { | |
236 | (String::new(), String::new(), String::new()) | |
237 | }; | |
238 | ||
239 | let span = first.span.to(second.span); | |
240 | ||
241 | span_lint_and_then(cx, | |
242 | ALMOST_SWAPPED, | |
243 | span, | |
244 | &format!("this looks like you are trying to swap{}", what), | |
245 | |diag| { | |
246 | if !what.is_empty() { | |
247 | diag.span_suggestion( | |
248 | span, | |
249 | "try", | |
250 | format!( | |
251 | "std::mem::swap({}, {})", | |
252 | lhs, | |
253 | rhs, | |
254 | ), | |
255 | Applicability::MaybeIncorrect, | |
256 | ); | |
257 | diag.note("or maybe you should use `std::mem::replace`?"); | |
258 | } | |
259 | }); | |
260 | } | |
261 | } | |
262 | } | |
263 | } |