]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/partialeq_to_none.rs
Rollup merge of #101279 - GuillaumeGomez:doc_auto_cfg_nested_impl, r=notriddle
[rust.git] / src / tools / clippy / clippy_lints / src / partialeq_to_none.rs
1 use clippy_utils::{
2     diagnostics::span_lint_and_sugg, is_lang_ctor, peel_hir_expr_refs, peel_ref_operators, sugg,
3     ty::is_type_diagnostic_item,
4 };
5 use rustc_errors::Applicability;
6 use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem};
7 use rustc_lint::{LateContext, LateLintPass};
8 use rustc_session::{declare_lint_pass, declare_tool_lint};
9 use rustc_span::sym;
10
11 declare_clippy_lint! {
12     /// ### What it does
13     ///
14     /// Checks for binary comparisons to a literal `Option::None`.
15     ///
16     /// ### Why is this bad?
17     ///
18     /// A programmer checking if some `foo` is `None` via a comparison `foo == None`
19     /// is usually inspired from other programming languages (e.g. `foo is None`
20     /// in Python).
21     /// Checking if a value of type `Option<T>` is (not) equal to `None` in that
22     /// way relies on `T: PartialEq` to do the comparison, which is unneeded.
23     ///
24     /// ### Example
25     /// ```rust
26     /// fn foo(f: Option<u32>) -> &'static str {
27     ///     if f != None { "yay" } else { "nay" }
28     /// }
29     /// ```
30     /// Use instead:
31     /// ```rust
32     /// fn foo(f: Option<u32>) -> &'static str {
33     ///     if f.is_some() { "yay" } else { "nay" }
34     /// }
35     /// ```
36     #[clippy::version = "1.64.0"]
37     pub PARTIALEQ_TO_NONE,
38     style,
39     "Binary comparison to `Option<T>::None` relies on `T: PartialEq`, which is unneeded"
40 }
41 declare_lint_pass!(PartialeqToNone => [PARTIALEQ_TO_NONE]);
42
43 impl<'tcx> LateLintPass<'tcx> for PartialeqToNone {
44     fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
45         // Skip expanded code, as we have no control over it anyway...
46         if e.span.from_expansion() {
47             return;
48         }
49
50         // If the expression is of type `Option`
51         let is_ty_option =
52             |expr: &Expr<'_>| is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr).peel_refs(), sym::Option);
53
54         // If the expression is a literal `Option::None`
55         let is_none_ctor = |expr: &Expr<'_>| {
56             !expr.span.from_expansion()
57                 && matches!(&peel_hir_expr_refs(expr).0.kind,
58             ExprKind::Path(p) if is_lang_ctor(cx, p, LangItem::OptionNone))
59         };
60
61         let mut applicability = Applicability::MachineApplicable;
62
63         if let ExprKind::Binary(op, left_side, right_side) = e.kind {
64             // All other comparisons (e.g. `>= None`) have special meaning wrt T
65             let is_eq = match op.node {
66                 BinOpKind::Eq => true,
67                 BinOpKind::Ne => false,
68                 _ => return,
69             };
70
71             // We are only interested in comparisons between `Option` and a literal `Option::None`
72             let scrutinee = match (
73                 is_none_ctor(left_side) && is_ty_option(right_side),
74                 is_none_ctor(right_side) && is_ty_option(left_side),
75             ) {
76                 (true, false) => right_side,
77                 (false, true) => left_side,
78                 _ => return,
79             };
80
81             // Peel away refs/derefs (as long as we don't cross manual deref impls), as
82             // autoref/autoderef will take care of those
83             let sugg = format!(
84                 "{}.{}",
85                 sugg::Sugg::hir_with_applicability(cx, peel_ref_operators(cx, scrutinee), "..", &mut applicability)
86                     .maybe_par(),
87                 if is_eq { "is_none()" } else { "is_some()" }
88             );
89
90             span_lint_and_sugg(
91                 cx,
92                 PARTIALEQ_TO_NONE,
93                 e.span,
94                 "binary comparison to literal `Option::None`",
95                 if is_eq {
96                     "use `Option::is_none()` instead"
97                 } else {
98                     "use `Option::is_some()` instead"
99                 },
100                 sugg,
101                 applicability,
102             );
103         }
104     }
105 }