| use clippy_utils::diagnostics::span_lint_and_sugg; | |
| use clippy_utils::res::{MaybeDef, MaybeQPath}; | |
| use clippy_utils::{peel_hir_expr_refs, peel_ref_operators, sugg}; | |
| use rustc_errors::Applicability; | |
| use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem}; | |
| use rustc_lint::{LateContext, LateLintPass}; | |
| use rustc_session::declare_lint_pass; | |
| 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 | |
| /// ```no_run | |
| /// fn foo(f: Option<u32>) -> &'static str { | |
| /// if f != None { "yay" } else { "nay" } | |
| /// } | |
| /// ``` | |
| /// Use instead: | |
| /// ```no_run | |
| /// fn foo(f: Option<u32>) -> &'static str { | |
| /// if f.is_some() { "yay" } else { "nay" } | |
| /// } | |
| /// ``` | |
| #[clippy::version = "1.65.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<'_>| { | |
| cx.typeck_results() | |
| .expr_ty(expr) | |
| .peel_refs() | |
| .is_diag_item(cx, sym::Option) | |
| }; | |
| // If the expression is a literal `Option::None` | |
| let is_none_ctor = |expr: &Expr<'_>| { | |
| !expr.span.from_expansion() | |
| && peel_hir_expr_refs(expr) | |
| .0 | |
| .res(cx) | |
| .ctor_parent(cx) | |
| .is_lang_item(cx, 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_paren(), | |
| 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, | |
| ); | |
| } | |
| } | |
| } |