104 lines
3.8 KiB
Rust
104 lines
3.8 KiB
Rust
use clippy_utils::diagnostics::span_lint_and_sugg;
|
|
use clippy_utils::ty::is_type_diagnostic_item;
|
|
use clippy_utils::{is_res_lang_ctor, path_res, 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<'_>| 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()
|
|
&& is_res_lang_ctor(cx, path_res(cx, peel_hir_expr_refs(expr).0), 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,
|
|
);
|
|
}
|
|
}
|
|
}
|