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