summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs
blob: 11c43247868ca46b817e01f60b13efe562a531b3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::source::snippet_with_macro_callsite;
use clippy_utils::{contains_return, higher, is_else_clause, is_lang_ctor, meets_msrv, msrvs, peel_blocks};
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{Expr, ExprKind, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};

declare_clippy_lint! {
    /// ### What it does
    /// Checks for if-else that could be written using either `bool::then` or `bool::then_some`.
    ///
    /// ### Why is this bad?
    /// Looks a little redundant. Using `bool::then` is more concise and incurs no loss of clarity.
    /// For simple calculations and known values, use `bool::then_some`, which is eagerly evaluated
    /// in comparison to `bool::then`.
    ///
    /// ### Example
    /// ```rust
    /// # let v = vec![0];
    /// let a = if v.is_empty() {
    ///     println!("true!");
    ///     Some(42)
    /// } else {
    ///     None
    /// };
    /// ```
    ///
    /// Could be written:
    ///
    /// ```rust
    /// # let v = vec![0];
    /// let a = v.is_empty().then(|| {
    ///     println!("true!");
    ///     42
    /// });
    /// ```
    #[clippy::version = "1.53.0"]
    pub IF_THEN_SOME_ELSE_NONE,
    restriction,
    "Finds if-else that could be written using either `bool::then` or `bool::then_some`"
}

pub struct IfThenSomeElseNone {
    msrv: Option<RustcVersion>,
}

impl IfThenSomeElseNone {
    #[must_use]
    pub fn new(msrv: Option<RustcVersion>) -> Self {
        Self { msrv }
    }
}

impl_lint_pass!(IfThenSomeElseNone => [IF_THEN_SOME_ELSE_NONE]);

impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
        if !meets_msrv(self.msrv, msrvs::BOOL_THEN) {
            return;
        }

        if in_external_macro(cx.sess(), expr.span) {
            return;
        }

        // We only care about the top-most `if` in the chain
        if is_else_clause(cx.tcx, expr) {
            return;
        }

        if let Some(higher::If { cond, then, r#else: Some(els) }) = higher::If::hir(expr)
            && let ExprKind::Block(then_block, _) = then.kind
            && let Some(then_expr) = then_block.expr
            && let ExprKind::Call(then_call, [then_arg]) = then_expr.kind
            && let ExprKind::Path(ref then_call_qpath) = then_call.kind
            && is_lang_ctor(cx, then_call_qpath, OptionSome)
            && let ExprKind::Path(ref qpath) = peel_blocks(els).kind
            && is_lang_ctor(cx, qpath, OptionNone)
            && !stmts_contains_early_return(then_block.stmts)
        {
            let cond_snip = snippet_with_macro_callsite(cx, cond.span, "[condition]");
            let cond_snip = if matches!(cond.kind, ExprKind::Unary(_, _) | ExprKind::Binary(_, _, _)) {
                format!("({})", cond_snip)
            } else {
                cond_snip.into_owned()
            };
            let arg_snip = snippet_with_macro_callsite(cx, then_arg.span, "");
            let mut method_body = if then_block.stmts.is_empty() {
                arg_snip.into_owned()
            } else {
                format!("{{ /* snippet */ {} }}", arg_snip)
            };
            let method_name = if switch_to_eager_eval(cx, expr) && meets_msrv(self.msrv, msrvs::BOOL_THEN_SOME) {
                "then_some"
            } else {
                method_body.insert_str(0, "|| ");
                "then"
            };

            let help = format!(
                "consider using `bool::{}` like: `{}.{}({})`",
                method_name, cond_snip, method_name, method_body,
            );
            span_lint_and_help(
                cx,
                IF_THEN_SOME_ELSE_NONE,
                expr.span,
                &format!("this could be simplified with `bool::{}`", method_name),
                None,
                &help,
            );
        }
    }

    extract_msrv_attr!(LateContext);
}

fn stmts_contains_early_return(stmts: &[Stmt<'_>]) -> bool {
    stmts.iter().any(|stmt| {
        let Stmt { kind: StmtKind::Semi(e), .. } = stmt else { return false };

        contains_return(e)
    })
}