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