summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/partialeq_to_none.rs
blob: 000b0ba7a148e75e503dd9f73ca2061ead430189 (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
use clippy_utils::{
    diagnostics::span_lint_and_sugg, is_lang_ctor, peel_hir_expr_refs, peel_ref_operators, sugg,
    ty::is_type_diagnostic_item,
};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;

declare_clippy_lint! {
    /// ### What it does
    ///
    /// Checks for binary comparisons to a literal `Option::None`.
    ///
    /// ### Why is this bad?
    ///
    /// A programmer checking if some `foo` is `None` via a comparison `foo == None`
    /// is usually inspired from other programming languages (e.g. `foo is None`
    /// in Python).
    /// Checking if a value of type `Option<T>` is (not) equal to `None` in that
    /// way relies on `T: PartialEq` to do the comparison, which is unneeded.
    ///
    /// ### Example
    /// ```rust
    /// fn foo(f: Option<u32>) -> &'static str {
    ///     if f != None { "yay" } else { "nay" }
    /// }
    /// ```
    /// Use instead:
    /// ```rust
    /// fn foo(f: Option<u32>) -> &'static str {
    ///     if f.is_some() { "yay" } else { "nay" }
    /// }
    /// ```
    #[clippy::version = "1.64.0"]
    pub PARTIALEQ_TO_NONE,
    style,
    "Binary comparison to `Option<T>::None` relies on `T: PartialEq`, which is unneeded"
}
declare_lint_pass!(PartialeqToNone => [PARTIALEQ_TO_NONE]);

impl<'tcx> LateLintPass<'tcx> for PartialeqToNone {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
        // Skip expanded code, as we have no control over it anyway...
        if e.span.from_expansion() {
            return;
        }

        // If the expression is of type `Option`
        let is_ty_option =
            |expr: &Expr<'_>| is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr).peel_refs(), sym::Option);

        // If the expression is a literal `Option::None`
        let is_none_ctor = |expr: &Expr<'_>| {
            !expr.span.from_expansion()
                && matches!(&peel_hir_expr_refs(expr).0.kind,
            ExprKind::Path(p) if is_lang_ctor(cx, p, LangItem::OptionNone))
        };

        let mut applicability = Applicability::MachineApplicable;

        if let ExprKind::Binary(op, left_side, right_side) = e.kind {
            // All other comparisons (e.g. `>= None`) have special meaning wrt T
            let is_eq = match op.node {
                BinOpKind::Eq => true,
                BinOpKind::Ne => false,
                _ => return,
            };

            // We are only interested in comparisons between `Option` and a literal `Option::None`
            let scrutinee = match (
                is_none_ctor(left_side) && is_ty_option(right_side),
                is_none_ctor(right_side) && is_ty_option(left_side),
            ) {
                (true, false) => right_side,
                (false, true) => left_side,
                _ => return,
            };

            // Peel away refs/derefs (as long as we don't cross manual deref impls), as
            // autoref/autoderef will take care of those
            let sugg = format!(
                "{}.{}",
                sugg::Sugg::hir_with_applicability(cx, peel_ref_operators(cx, scrutinee), "..", &mut applicability)
                    .maybe_par(),
                if is_eq { "is_none()" } else { "is_some()" }
            );

            span_lint_and_sugg(
                cx,
                PARTIALEQ_TO_NONE,
                e.span,
                "binary comparison to literal `Option::None`",
                if is_eq {
                    "use `Option::is_none()` instead"
                } else {
                    "use `Option::is_some()` instead"
                },
                sugg,
                applicability,
            );
        }
    }
}