]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/needless_bool.rs
Auto merge of #79965 - ijackson:moreerrnos, r=joshtriplett
[rust.git] / src / tools / clippy / clippy_lints / src / needless_bool.rs
1 //! Checks for needless boolean results of if-else expressions
2 //!
3 //! This lint is **warn** by default
4
5 use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
6 use clippy_utils::source::snippet_with_applicability;
7 use clippy_utils::sugg::Sugg;
8 use clippy_utils::{is_else_clause, is_expn_of};
9 use rustc_ast::ast::LitKind;
10 use rustc_errors::Applicability;
11 use rustc_hir::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp};
12 use rustc_lint::{LateContext, LateLintPass};
13 use rustc_session::{declare_lint_pass, declare_tool_lint};
14 use rustc_span::source_map::Spanned;
15 use rustc_span::Span;
16
17 declare_clippy_lint! {
18     /// **What it does:** Checks for expressions of the form `if c { true } else {
19     /// false }` (or vice versa) and suggests using the condition directly.
20     ///
21     /// **Why is this bad?** Redundant code.
22     ///
23     /// **Known problems:** Maybe false positives: Sometimes, the two branches are
24     /// painstakingly documented (which we, of course, do not detect), so they *may*
25     /// have some value. Even then, the documentation can be rewritten to match the
26     /// shorter code.
27     ///
28     /// **Example:**
29     /// ```rust,ignore
30     /// if x {
31     ///     false
32     /// } else {
33     ///     true
34     /// }
35     /// ```
36     /// Could be written as
37     /// ```rust,ignore
38     /// !x
39     /// ```
40     pub NEEDLESS_BOOL,
41     complexity,
42     "if-statements with plain booleans in the then- and else-clause, e.g., `if p { true } else { false }`"
43 }
44
45 declare_clippy_lint! {
46     /// **What it does:** Checks for expressions of the form `x == true`,
47     /// `x != true` and order comparisons such as `x < true` (or vice versa) and
48     /// suggest using the variable directly.
49     ///
50     /// **Why is this bad?** Unnecessary code.
51     ///
52     /// **Known problems:** None.
53     ///
54     /// **Example:**
55     /// ```rust,ignore
56     /// if x == true {}
57     /// if y == false {}
58     /// ```
59     /// use `x` directly:
60     /// ```rust,ignore
61     /// if x {}
62     /// if !y {}
63     /// ```
64     pub BOOL_COMPARISON,
65     complexity,
66     "comparing a variable to a boolean, e.g., `if x == true` or `if x != true`"
67 }
68
69 declare_lint_pass!(NeedlessBool => [NEEDLESS_BOOL]);
70
71 impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
72     fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
73         use self::Expression::{Bool, RetBool};
74         if let ExprKind::If(pred, then_block, Some(else_expr)) = e.kind {
75             let reduce = |ret, not| {
76                 let mut applicability = Applicability::MachineApplicable;
77                 let snip = Sugg::hir_with_applicability(cx, pred, "<predicate>", &mut applicability);
78                 let mut snip = if not { !snip } else { snip };
79
80                 if ret {
81                     snip = snip.make_return();
82                 }
83
84                 if is_else_clause(cx.tcx, e) {
85                     snip = snip.blockify();
86                 }
87
88                 span_lint_and_sugg(
89                     cx,
90                     NEEDLESS_BOOL,
91                     e.span,
92                     "this if-then-else expression returns a bool literal",
93                     "you can reduce it to",
94                     snip.to_string(),
95                     applicability,
96                 );
97             };
98             if let ExprKind::Block(then_block, _) = then_block.kind {
99                 match (fetch_bool_block(then_block), fetch_bool_expr(else_expr)) {
100                     (RetBool(true), RetBool(true)) | (Bool(true), Bool(true)) => {
101                         span_lint(
102                             cx,
103                             NEEDLESS_BOOL,
104                             e.span,
105                             "this if-then-else expression will always return true",
106                         );
107                     },
108                     (RetBool(false), RetBool(false)) | (Bool(false), Bool(false)) => {
109                         span_lint(
110                             cx,
111                             NEEDLESS_BOOL,
112                             e.span,
113                             "this if-then-else expression will always return false",
114                         );
115                     },
116                     (RetBool(true), RetBool(false)) => reduce(true, false),
117                     (Bool(true), Bool(false)) => reduce(false, false),
118                     (RetBool(false), RetBool(true)) => reduce(true, true),
119                     (Bool(false), Bool(true)) => reduce(false, true),
120                     _ => (),
121                 }
122             } else {
123                 panic!("IfExpr `then` node is not an `ExprKind::Block`");
124             }
125         }
126     }
127 }
128
129 declare_lint_pass!(BoolComparison => [BOOL_COMPARISON]);
130
131 impl<'tcx> LateLintPass<'tcx> for BoolComparison {
132     fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
133         if e.span.from_expansion() {
134             return;
135         }
136
137         if let ExprKind::Binary(Spanned { node, .. }, ..) = e.kind {
138             let ignore_case = None::<(fn(_) -> _, &str)>;
139             let ignore_no_literal = None::<(fn(_, _) -> _, &str)>;
140             match node {
141                 BinOpKind::Eq => {
142                     let true_case = Some((|h| h, "equality checks against true are unnecessary"));
143                     let false_case = Some((
144                         |h: Sugg<'_>| !h,
145                         "equality checks against false can be replaced by a negation",
146                     ));
147                     check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal);
148                 },
149                 BinOpKind::Ne => {
150                     let true_case = Some((
151                         |h: Sugg<'_>| !h,
152                         "inequality checks against true can be replaced by a negation",
153                     ));
154                     let false_case = Some((|h| h, "inequality checks against false are unnecessary"));
155                     check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal);
156                 },
157                 BinOpKind::Lt => check_comparison(
158                     cx,
159                     e,
160                     ignore_case,
161                     Some((|h| h, "greater than checks against false are unnecessary")),
162                     Some((
163                         |h: Sugg<'_>| !h,
164                         "less than comparison against true can be replaced by a negation",
165                     )),
166                     ignore_case,
167                     Some((
168                         |l: Sugg<'_>, r: Sugg<'_>| (!l).bit_and(&r),
169                         "order comparisons between booleans can be simplified",
170                     )),
171                 ),
172                 BinOpKind::Gt => check_comparison(
173                     cx,
174                     e,
175                     Some((
176                         |h: Sugg<'_>| !h,
177                         "less than comparison against true can be replaced by a negation",
178                     )),
179                     ignore_case,
180                     ignore_case,
181                     Some((|h| h, "greater than checks against false are unnecessary")),
182                     Some((
183                         |l: Sugg<'_>, r: Sugg<'_>| l.bit_and(&(!r)),
184                         "order comparisons between booleans can be simplified",
185                     )),
186                 ),
187                 _ => (),
188             }
189         }
190     }
191 }
192
193 struct ExpressionInfoWithSpan {
194     one_side_is_unary_not: bool,
195     left_span: Span,
196     right_span: Span,
197 }
198
199 fn is_unary_not(e: &Expr<'_>) -> (bool, Span) {
200     if let ExprKind::Unary(UnOp::Not, operand) = e.kind {
201         return (true, operand.span);
202     }
203     (false, e.span)
204 }
205
206 fn one_side_is_unary_not<'tcx>(left_side: &'tcx Expr<'_>, right_side: &'tcx Expr<'_>) -> ExpressionInfoWithSpan {
207     let left = is_unary_not(left_side);
208     let right = is_unary_not(right_side);
209
210     ExpressionInfoWithSpan {
211         one_side_is_unary_not: left.0 != right.0,
212         left_span: left.1,
213         right_span: right.1,
214     }
215 }
216
217 fn check_comparison<'a, 'tcx>(
218     cx: &LateContext<'tcx>,
219     e: &'tcx Expr<'_>,
220     left_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>,
221     left_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>,
222     right_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>,
223     right_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>,
224     no_literal: Option<(impl FnOnce(Sugg<'a>, Sugg<'a>) -> Sugg<'a>, &str)>,
225 ) {
226     use self::Expression::{Bool, Other};
227
228     if let ExprKind::Binary(op, left_side, right_side) = e.kind {
229         let (l_ty, r_ty) = (
230             cx.typeck_results().expr_ty(left_side),
231             cx.typeck_results().expr_ty(right_side),
232         );
233         if is_expn_of(left_side.span, "cfg").is_some() || is_expn_of(right_side.span, "cfg").is_some() {
234             return;
235         }
236         if l_ty.is_bool() && r_ty.is_bool() {
237             let mut applicability = Applicability::MachineApplicable;
238
239             if let BinOpKind::Eq = op.node {
240                 let expression_info = one_side_is_unary_not(left_side, right_side);
241                 if expression_info.one_side_is_unary_not {
242                     span_lint_and_sugg(
243                         cx,
244                         BOOL_COMPARISON,
245                         e.span,
246                         "this comparison might be written more concisely",
247                         "try simplifying it as shown",
248                         format!(
249                             "{} != {}",
250                             snippet_with_applicability(cx, expression_info.left_span, "..", &mut applicability),
251                             snippet_with_applicability(cx, expression_info.right_span, "..", &mut applicability)
252                         ),
253                         applicability,
254                     );
255                 }
256             }
257
258             match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) {
259                 (Bool(true), Other) => left_true.map_or((), |(h, m)| {
260                     suggest_bool_comparison(cx, e, right_side, applicability, m, h);
261                 }),
262                 (Other, Bool(true)) => right_true.map_or((), |(h, m)| {
263                     suggest_bool_comparison(cx, e, left_side, applicability, m, h);
264                 }),
265                 (Bool(false), Other) => left_false.map_or((), |(h, m)| {
266                     suggest_bool_comparison(cx, e, right_side, applicability, m, h);
267                 }),
268                 (Other, Bool(false)) => right_false.map_or((), |(h, m)| {
269                     suggest_bool_comparison(cx, e, left_side, applicability, m, h);
270                 }),
271                 (Other, Other) => no_literal.map_or((), |(h, m)| {
272                     let left_side = Sugg::hir_with_applicability(cx, left_side, "..", &mut applicability);
273                     let right_side = Sugg::hir_with_applicability(cx, right_side, "..", &mut applicability);
274                     span_lint_and_sugg(
275                         cx,
276                         BOOL_COMPARISON,
277                         e.span,
278                         m,
279                         "try simplifying it as shown",
280                         h(left_side, right_side).to_string(),
281                         applicability,
282                     );
283                 }),
284                 _ => (),
285             }
286         }
287     }
288 }
289
290 fn suggest_bool_comparison<'a, 'tcx>(
291     cx: &LateContext<'tcx>,
292     e: &'tcx Expr<'_>,
293     expr: &Expr<'_>,
294     mut applicability: Applicability,
295     message: &str,
296     conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>,
297 ) {
298     let hint = if expr.span.from_expansion() {
299         if applicability != Applicability::Unspecified {
300             applicability = Applicability::MaybeIncorrect;
301         }
302         Sugg::hir_with_macro_callsite(cx, expr, "..")
303     } else {
304         Sugg::hir_with_applicability(cx, expr, "..", &mut applicability)
305     };
306     span_lint_and_sugg(
307         cx,
308         BOOL_COMPARISON,
309         e.span,
310         message,
311         "try simplifying it as shown",
312         conv_hint(hint).to_string(),
313         applicability,
314     );
315 }
316
317 enum Expression {
318     Bool(bool),
319     RetBool(bool),
320     Other,
321 }
322
323 fn fetch_bool_block(block: &Block<'_>) -> Expression {
324     match (&*block.stmts, block.expr.as_ref()) {
325         (&[], Some(e)) => fetch_bool_expr(&**e),
326         (&[ref e], None) => {
327             if let StmtKind::Semi(e) = e.kind {
328                 if let ExprKind::Ret(_) = e.kind {
329                     fetch_bool_expr(e)
330                 } else {
331                     Expression::Other
332                 }
333             } else {
334                 Expression::Other
335             }
336         },
337         _ => Expression::Other,
338     }
339 }
340
341 fn fetch_bool_expr(expr: &Expr<'_>) -> Expression {
342     match expr.kind {
343         ExprKind::Block(block, _) => fetch_bool_block(block),
344         ExprKind::Lit(ref lit_ptr) => {
345             if let LitKind::Bool(value) = lit_ptr.node {
346                 Expression::Bool(value)
347             } else {
348                 Expression::Other
349             }
350         },
351         ExprKind::Ret(Some(expr)) => match fetch_bool_expr(expr) {
352             Expression::Bool(value) => Expression::RetBool(value),
353             _ => Expression::Other,
354         },
355         _ => Expression::Other,
356     }
357 }