|
| 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 | + matches!(&peel_hir_expr_refs(expr).0.kind, |
| 57 | + ExprKind::Path(p) if is_lang_ctor(cx, p, LangItem::OptionNone)) |
| 58 | + }; |
| 59 | + |
| 60 | + let mut applicability = Applicability::MachineApplicable; |
| 61 | + |
| 62 | + if let ExprKind::Binary(op, left_side, right_side) = e.kind { |
| 63 | + // All other comparisons (e.g. `>= None`) have special meaning wrt T |
| 64 | + let is_eq = match op.node { |
| 65 | + BinOpKind::Eq => true, |
| 66 | + BinOpKind::Ne => false, |
| 67 | + _ => return, |
| 68 | + }; |
| 69 | + |
| 70 | + // We are only interested in comparisons between `Option` and a literal `Option::None` |
| 71 | + let scrutinee = match ( |
| 72 | + is_none_ctor(left_side) && is_ty_option(right_side), |
| 73 | + is_none_ctor(right_side) && is_ty_option(left_side), |
| 74 | + ) { |
| 75 | + (true, false) => right_side, |
| 76 | + (false, true) => left_side, |
| 77 | + _ => return, |
| 78 | + }; |
| 79 | + |
| 80 | + // Peel away refs/derefs (as long as we don't cross manual deref impls), as |
| 81 | + // autoref/autoderef will take care of those |
| 82 | + let sugg = format!( |
| 83 | + "{}.{}", |
| 84 | + sugg::Sugg::hir_with_applicability(cx, peel_ref_operators(cx, scrutinee), "..", &mut applicability) |
| 85 | + .maybe_par(), |
| 86 | + if is_eq { "is_none()" } else { "is_some()" } |
| 87 | + ); |
| 88 | + |
| 89 | + span_lint_and_sugg( |
| 90 | + cx, |
| 91 | + PARTIALEQ_TO_NONE, |
| 92 | + e.span, |
| 93 | + "binary comparison to literal `Option::None`", |
| 94 | + if is_eq { |
| 95 | + "use `Option::is_none()` instead" |
| 96 | + } else { |
| 97 | + "use `Option::is_some()` instead" |
| 98 | + }, |
| 99 | + sugg, |
| 100 | + applicability, |
| 101 | + ); |
| 102 | + } |
| 103 | + } |
| 104 | +} |
0 commit comments