]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/copies.rs
Merge remote-tracking branch 'upstream/master' into rustup
[rust.git] / clippy_lints / src / copies.rs
1 use clippy_utils::diagnostics::span_lint_and_note;
2 use clippy_utils::{eq_expr_value, in_macro, search_same, SpanlessEq, SpanlessHash};
3 use clippy_utils::{get_parent_expr, if_sequence};
4 use rustc_hir::{Block, Expr, ExprKind};
5 use rustc_lint::{LateContext, LateLintPass};
6 use rustc_session::{declare_lint_pass, declare_tool_lint};
7
8 declare_clippy_lint! {
9     /// **What it does:** Checks for consecutive `if`s with the same condition.
10     ///
11     /// **Why is this bad?** This is probably a copy & paste error.
12     ///
13     /// **Known problems:** Hopefully none.
14     ///
15     /// **Example:**
16     /// ```ignore
17     /// if a == b {
18     ///     …
19     /// } else if a == b {
20     ///     …
21     /// }
22     /// ```
23     ///
24     /// Note that this lint ignores all conditions with a function call as it could
25     /// have side effects:
26     ///
27     /// ```ignore
28     /// if foo() {
29     ///     …
30     /// } else if foo() { // not linted
31     ///     …
32     /// }
33     /// ```
34     pub IFS_SAME_COND,
35     correctness,
36     "consecutive `if`s with the same condition"
37 }
38
39 declare_clippy_lint! {
40     /// **What it does:** Checks for consecutive `if`s with the same function call.
41     ///
42     /// **Why is this bad?** This is probably a copy & paste error.
43     /// Despite the fact that function can have side effects and `if` works as
44     /// intended, such an approach is implicit and can be considered a "code smell".
45     ///
46     /// **Known problems:** Hopefully none.
47     ///
48     /// **Example:**
49     /// ```ignore
50     /// if foo() == bar {
51     ///     …
52     /// } else if foo() == bar {
53     ///     …
54     /// }
55     /// ```
56     ///
57     /// This probably should be:
58     /// ```ignore
59     /// if foo() == bar {
60     ///     …
61     /// } else if foo() == baz {
62     ///     …
63     /// }
64     /// ```
65     ///
66     /// or if the original code was not a typo and called function mutates a state,
67     /// consider move the mutation out of the `if` condition to avoid similarity to
68     /// a copy & paste error:
69     ///
70     /// ```ignore
71     /// let first = foo();
72     /// if first == bar {
73     ///     …
74     /// } else {
75     ///     let second = foo();
76     ///     if second == bar {
77     ///     …
78     ///     }
79     /// }
80     /// ```
81     pub SAME_FUNCTIONS_IN_IF_CONDITION,
82     pedantic,
83     "consecutive `if`s with the same function call"
84 }
85
86 declare_clippy_lint! {
87     /// **What it does:** Checks for `if/else` with the same body as the *then* part
88     /// and the *else* part.
89     ///
90     /// **Why is this bad?** This is probably a copy & paste error.
91     ///
92     /// **Known problems:** Hopefully none.
93     ///
94     /// **Example:**
95     /// ```ignore
96     /// let foo = if … {
97     ///     42
98     /// } else {
99     ///     42
100     /// };
101     /// ```
102     pub IF_SAME_THEN_ELSE,
103     correctness,
104     "`if` with the same `then` and `else` blocks"
105 }
106
107 declare_lint_pass!(CopyAndPaste => [IFS_SAME_COND, SAME_FUNCTIONS_IN_IF_CONDITION, IF_SAME_THEN_ELSE]);
108
109 impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
110     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
111         if !expr.span.from_expansion() {
112             // skip ifs directly in else, it will be checked in the parent if
113             if let Some(&Expr {
114                 kind: ExprKind::If(_, _, Some(ref else_expr)),
115                 ..
116             }) = get_parent_expr(cx, expr)
117             {
118                 if else_expr.hir_id == expr.hir_id {
119                     return;
120                 }
121             }
122
123             let (conds, blocks) = if_sequence(expr);
124             lint_same_then_else(cx, &blocks);
125             lint_same_cond(cx, &conds);
126             lint_same_fns_in_if_cond(cx, &conds);
127         }
128     }
129 }
130
131 /// Implementation of `IF_SAME_THEN_ELSE`.
132 fn lint_same_then_else(cx: &LateContext<'_>, blocks: &[&Block<'_>]) {
133     let eq: &dyn Fn(&&Block<'_>, &&Block<'_>) -> bool =
134         &|&lhs, &rhs| -> bool { SpanlessEq::new(cx).eq_block(lhs, rhs) };
135
136     if let Some((i, j)) = search_same_sequenced(blocks, eq) {
137         span_lint_and_note(
138             cx,
139             IF_SAME_THEN_ELSE,
140             j.span,
141             "this `if` has identical blocks",
142             Some(i.span),
143             "same as this",
144         );
145     }
146 }
147
148 /// Implementation of `IFS_SAME_COND`.
149 fn lint_same_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
150     let hash: &dyn Fn(&&Expr<'_>) -> u64 = &|expr| -> u64 {
151         let mut h = SpanlessHash::new(cx);
152         h.hash_expr(expr);
153         h.finish()
154     };
155
156     let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { eq_expr_value(cx, lhs, rhs) };
157
158     for (i, j) in search_same(conds, hash, eq) {
159         span_lint_and_note(
160             cx,
161             IFS_SAME_COND,
162             j.span,
163             "this `if` has the same condition as a previous `if`",
164             Some(i.span),
165             "same as this",
166         );
167     }
168 }
169
170 /// Implementation of `SAME_FUNCTIONS_IN_IF_CONDITION`.
171 fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
172     let hash: &dyn Fn(&&Expr<'_>) -> u64 = &|expr| -> u64 {
173         let mut h = SpanlessHash::new(cx);
174         h.hash_expr(expr);
175         h.finish()
176     };
177
178     let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool {
179         // Do not lint if any expr originates from a macro
180         if in_macro(lhs.span) || in_macro(rhs.span) {
181             return false;
182         }
183         // Do not spawn warning if `IFS_SAME_COND` already produced it.
184         if eq_expr_value(cx, lhs, rhs) {
185             return false;
186         }
187         SpanlessEq::new(cx).eq_expr(lhs, rhs)
188     };
189
190     for (i, j) in search_same(conds, hash, eq) {
191         span_lint_and_note(
192             cx,
193             SAME_FUNCTIONS_IN_IF_CONDITION,
194             j.span,
195             "this `if` has the same function call as a previous `if`",
196             Some(i.span),
197             "same as this",
198         );
199     }
200 }
201
202 fn search_same_sequenced<T, Eq>(exprs: &[T], eq: Eq) -> Option<(&T, &T)>
203 where
204     Eq: Fn(&T, &T) -> bool,
205 {
206     for win in exprs.windows(2) {
207         if eq(&win[0], &win[1]) {
208             return Some((&win[0], &win[1]));
209         }
210     }
211     None
212 }