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