]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/swap.rs
lower let-else in MIR instead
[rust.git] / clippy_lints / src / swap.rs
1 use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
2 use clippy_utils::source::snippet_with_applicability;
3 use clippy_utils::sugg::Sugg;
4 use clippy_utils::ty::is_type_diagnostic_item;
5 use clippy_utils::{can_mut_borrow_both, eq_expr_value, std_or_core};
6 use if_chain::if_chain;
7 use rustc_errors::Applicability;
8 use rustc_hir::{BinOpKind, Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
9 use rustc_lint::{LateContext, LateLintPass};
10 use rustc_middle::ty;
11 use rustc_session::{declare_lint_pass, declare_tool_lint};
12 use rustc_span::source_map::Spanned;
13 use rustc_span::{sym, Span};
14
15 declare_clippy_lint! {
16     /// ### What it does
17     /// Checks for manual swapping.
18     ///
19     /// ### Why is this bad?
20     /// The `std::mem::swap` function exposes the intent better
21     /// without deinitializing or copying either variable.
22     ///
23     /// ### Example
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     /// ```
38     #[clippy::version = "pre 1.29.0"]
39     pub MANUAL_SWAP,
40     complexity,
41     "manual swap of two variables"
42 }
43
44 declare_clippy_lint! {
45     /// ### What it does
46     /// Checks for `foo = bar; bar = foo` sequences.
47     ///
48     /// ### Why is this bad?
49     /// This looks like a failed attempt to swap.
50     ///
51     /// ### Example
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     /// ```
64     #[clippy::version = "pre 1.29.0"]
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);
76         check_xor_swap(cx, block);
77     }
78 }
79
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(_, _))
91                         || is_type_diagnostic_item(cx, ty, sym::Vec)
92                         || is_type_diagnostic_item(cx, ty, sym::VecDeque)
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);
118     let Some(sugg) = std_or_core(cx) else { return };
119
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",
129                 format!("{}::mem::swap({}, {})", sugg, first.mut_addr(), second.mut_addr()),
130                 applicability,
131             );
132             if !is_xor_based {
133                 diag.note(&format!("or maybe you should use `{}::mem::replace`?", sugg));
134             }
135         },
136     );
137 }
138
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();
144             if let StmtKind::Local(tmp, _) = w[0].kind;
145             if let Some(tmp_init) = tmp.init;
146             if let PatKind::Binding(.., ident, None) = tmp.pat.kind;
147
148             // foo() = bar();
149             if let StmtKind::Semi(first) = w[1].kind;
150             if let ExprKind::Assign(lhs1, rhs1, _) = first.kind;
151
152             // bar() = t;
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;
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 {
162                 let span = w[0].span.to(second.span);
163                 generate_swap_warning(cx, lhs1, lhs2, span, false);
164             }
165         }
166     }
167 }
168
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! {
173             if let StmtKind::Semi(first) = w[0].kind;
174             if let StmtKind::Semi(second) = w[1].kind;
175             if first.span.ctxt() == second.span.ctxt();
176             if let ExprKind::Assign(lhs0, rhs0, _) = first.kind;
177             if let ExprKind::Assign(lhs1, rhs1, _) = second.kind;
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);
194                 let Some(sugg) = std_or_core(cx) else { return };
195
196                 span_lint_and_then(cx,
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                     });
218             }
219         }
220     }
221 }
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
242 /// Returns the lhs and rhs of an xor assignment statement.
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 }