]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/bool_to_int_with_if.rs
Rollup merge of #104901 - krtab:filetype_compare, r=the8472
[rust.git] / src / tools / clippy / clippy_lints / src / bool_to_int_with_if.rs
1 use clippy_utils::higher::If;
2 use rustc_ast::LitKind;
3 use rustc_hir::{Block, ExprKind};
4 use rustc_lint::{LateContext, LateLintPass};
5 use rustc_session::{declare_lint_pass, declare_tool_lint};
6
7 use clippy_utils::{diagnostics::span_lint_and_then, in_constant, is_else_clause, is_integer_literal, sugg::Sugg};
8 use rustc_errors::Applicability;
9
10 declare_clippy_lint! {
11     /// ### What it does
12     /// Instead of using an if statement to convert a bool to an int,
13     /// this lint suggests using a `from()` function or an `as` coercion.
14     ///
15     /// ### Why is this bad?
16     /// Coercion or `from()` is another way to convert bool to a number.
17     /// Both methods are guaranteed to return 1 for true, and 0 for false.
18     ///
19     /// See https://doc.rust-lang.org/std/primitive.bool.html#impl-From%3Cbool%3E
20     ///
21     /// ### Example
22     /// ```rust
23     /// # let condition = false;
24     /// if condition {
25     ///     1_i64
26     /// } else {
27     ///     0
28     /// };
29     /// ```
30     /// Use instead:
31     /// ```rust
32     /// # let condition = false;
33     /// i64::from(condition);
34     /// ```
35     /// or
36     /// ```rust
37     /// # let condition = false;
38     /// condition as i64;
39     /// ```
40     #[clippy::version = "1.65.0"]
41     pub BOOL_TO_INT_WITH_IF,
42     pedantic,
43     "using if to convert bool to int"
44 }
45 declare_lint_pass!(BoolToIntWithIf => [BOOL_TO_INT_WITH_IF]);
46
47 impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf {
48     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
49         if !expr.span.from_expansion() && !in_constant(cx, expr.hir_id) {
50             check_if_else(cx, expr);
51         }
52     }
53 }
54
55 fn check_if_else<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
56     if let Some(If { cond, then, r#else: Some(r#else) }) = If::hir(expr)
57         && let Some(then_lit) = int_literal(then)
58         && let Some(else_lit) = int_literal(r#else)
59     {
60         let inverted = if is_integer_literal(then_lit, 1) && is_integer_literal(else_lit, 0) {
61             false
62         } else if is_integer_literal(then_lit, 0) && is_integer_literal(else_lit, 1) {
63             true
64         } else {
65             // Expression isn't boolean, exit
66             return;
67         };
68         let mut applicability = Applicability::MachineApplicable;
69         let snippet = {
70             let mut sugg = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability);
71             if inverted {
72                 sugg = !sugg;
73             }
74             sugg
75         };
76
77         let ty = cx.typeck_results().expr_ty(then_lit); // then and else must be of same type
78
79         let suggestion = {
80             let wrap_in_curly = is_else_clause(cx.tcx, expr);
81             let mut s = Sugg::NonParen(format!("{ty}::from({snippet})").into());
82             if wrap_in_curly {
83                 s = s.blockify();
84             }
85             s
86         }; // when used in else clause if statement should be wrapped in curly braces
87
88         let into_snippet = snippet.clone().maybe_par();
89         let as_snippet = snippet.as_ty(ty);
90
91         span_lint_and_then(cx,
92             BOOL_TO_INT_WITH_IF,
93             expr.span,
94             "boolean to int conversion using if",
95             |diag| {
96             diag.span_suggestion(
97                 expr.span,
98                 "replace with from",
99                 suggestion,
100                 applicability,
101             );
102             diag.note(format!("`{as_snippet}` or `{into_snippet}.into()` can also be valid options"));
103         });
104     };
105 }
106
107 // If block contains only a int literal expression, return literal expression
108 fn int_literal<'tcx>(expr: &'tcx rustc_hir::Expr<'tcx>) -> Option<&'tcx rustc_hir::Expr<'tcx>> {
109     if let ExprKind::Block(block, _) = expr.kind
110         && let Block {
111             stmts: [],       // Shouldn't lint if statements with side effects
112             expr: Some(expr),
113             ..
114         } = block
115         && let ExprKind::Lit(lit) = &expr.kind
116         && let LitKind::Int(_, _) = lit.node
117     {
118         Some(expr)
119     } else {
120         None
121     }
122 }