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