blob: a81c4dc6a7931c936dff2633e6359bb3428847c9 [file] [log] [blame] [edit]
use clippy_config::Conf;
use clippy_utils::consts::ConstEvalCtxt;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::MaybeResPath;
use clippy_utils::source::SpanRangeExt;
use rustc_errors::Applicability;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_hir::{BinOpKind, Constness, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
use rustc_middle::ty::TyCtxt;
use rustc_session::impl_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Checks for manual `is_infinite` reimplementations
/// (i.e., `x == <float>::INFINITY || x == <float>::NEG_INFINITY`).
///
/// ### Why is this bad?
/// The method `is_infinite` is shorter and more readable.
///
/// ### Example
/// ```no_run
/// # let x = 1.0f32;
/// if x == f32::INFINITY || x == f32::NEG_INFINITY {}
/// ```
/// Use instead:
/// ```no_run
/// # let x = 1.0f32;
/// if x.is_infinite() {}
/// ```
#[clippy::version = "1.73.0"]
pub MANUAL_IS_INFINITE,
style,
"use dedicated method to check if a float is infinite"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for manual `is_finite` reimplementations
/// (i.e., `x != <float>::INFINITY && x != <float>::NEG_INFINITY`).
///
/// ### Why is this bad?
/// The method `is_finite` is shorter and more readable.
///
/// ### Example
/// ```no_run
/// # let x = 1.0f32;
/// if x != f32::INFINITY && x != f32::NEG_INFINITY {}
/// if x.abs() < f32::INFINITY {}
/// ```
/// Use instead:
/// ```no_run
/// # let x = 1.0f32;
/// if x.is_finite() {}
/// if x.is_finite() {}
/// ```
#[clippy::version = "1.73.0"]
pub MANUAL_IS_FINITE,
style,
"use dedicated method to check if a float is finite"
}
impl_lint_pass!(ManualFloatMethods => [MANUAL_IS_INFINITE, MANUAL_IS_FINITE]);
#[derive(Clone, Copy)]
enum Variant {
ManualIsInfinite,
ManualIsFinite,
}
impl Variant {
pub fn lint(self) -> &'static Lint {
match self {
Self::ManualIsInfinite => MANUAL_IS_INFINITE,
Self::ManualIsFinite => MANUAL_IS_FINITE,
}
}
pub fn msg(self) -> &'static str {
match self {
Self::ManualIsInfinite => "manually checking if a float is infinite",
Self::ManualIsFinite => "manually checking if a float is finite",
}
}
}
pub struct ManualFloatMethods {
msrv: Msrv,
}
impl ManualFloatMethods {
pub fn new(conf: &'static Conf) -> Self {
Self { msrv: conf.msrv }
}
}
fn is_not_const(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
match tcx.def_kind(def_id) {
DefKind::Mod
| DefKind::Struct
| DefKind::Union
| DefKind::Enum
| DefKind::Variant
| DefKind::Trait
| DefKind::TyAlias
| DefKind::ForeignTy
| DefKind::TraitAlias
| DefKind::AssocTy
| DefKind::Macro(..)
| DefKind::Field
| DefKind::LifetimeParam
| DefKind::ExternCrate
| DefKind::Use
| DefKind::ForeignMod
| DefKind::GlobalAsm
| DefKind::Impl { .. }
| DefKind::OpaqueTy
| DefKind::SyntheticCoroutineBody
| DefKind::TyParam => true,
DefKind::AnonConst
| DefKind::InlineConst
| DefKind::Const
| DefKind::ConstParam
| DefKind::Static { .. }
| DefKind::Ctor(..)
| DefKind::AssocConst => false,
DefKind::Fn | DefKind::AssocFn | DefKind::Closure => tcx.constness(def_id) == Constness::NotConst,
}
}
impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let ExprKind::Binary(kind, lhs, rhs) = expr.kind
&& let ExprKind::Binary(lhs_kind, lhs_lhs, lhs_rhs) = lhs.kind
&& let ExprKind::Binary(rhs_kind, rhs_lhs, rhs_rhs) = rhs.kind
// Checking all possible scenarios using a function would be a hopeless task, as we have
// 16 possible alignments of constants/operands. For now, let's use `partition`.
&& let mut exprs = [lhs_lhs, lhs_rhs, rhs_lhs, rhs_rhs]
&& exprs.iter_mut().partition_in_place(|i| i.res_local_id().is_some()) == 2
&& !expr.span.in_external_macro(cx.sess().source_map())
&& (
is_not_const(cx.tcx, cx.tcx.hir_enclosing_body_owner(expr.hir_id).into())
|| self.msrv.meets(cx, msrvs::CONST_FLOAT_CLASSIFY)
)
&& let [first, second, const_1, const_2] = exprs
&& let ecx = ConstEvalCtxt::new(cx)
&& let ctxt = expr.span.ctxt()
&& let Some(const_1) = ecx.eval_local(const_1, ctxt)
&& let Some(const_2) = ecx.eval_local(const_2, ctxt)
&& first.res_local_id().is_some_and(|f| second.res_local_id().is_some_and(|s| f == s))
// The actual infinity check, we also allow `NEG_INFINITY` before` INFINITY` just in
// case somebody does that for some reason
&& (const_1.is_pos_infinity() && const_2.is_neg_infinity()
|| const_1.is_neg_infinity() && const_2.is_pos_infinity())
&& let Some(local_snippet) = first.span.get_source_text(cx)
{
let variant = match (kind.node, lhs_kind.node, rhs_kind.node) {
(BinOpKind::Or, BinOpKind::Eq, BinOpKind::Eq) => Variant::ManualIsInfinite,
(BinOpKind::And, BinOpKind::Ne, BinOpKind::Ne) => Variant::ManualIsFinite,
_ => return,
};
if is_from_proc_macro(cx, expr) {
return;
}
span_lint_and_then(cx, variant.lint(), expr.span, variant.msg(), |diag| {
match variant {
Variant::ManualIsInfinite => {
diag.span_suggestion(
expr.span,
"use the dedicated method instead",
format!("{local_snippet}.is_infinite()"),
Applicability::MachineApplicable,
);
},
Variant::ManualIsFinite => {
// TODO: There's probably some better way to do this, i.e., create
// multiple suggestions with notes between each of them
diag.span_suggestion_verbose(
expr.span,
"use the dedicated method instead",
format!("{local_snippet}.is_finite()"),
Applicability::MaybeIncorrect,
)
.span_suggestion_verbose(
expr.span,
"this will alter how it handles NaN; if that is a problem, use instead",
format!("{local_snippet}.is_finite() || {local_snippet}.is_nan()"),
Applicability::MaybeIncorrect,
)
.span_suggestion_verbose(
expr.span,
"or, for conciseness",
format!("!{local_snippet}.is_infinite()"),
Applicability::MaybeIncorrect,
);
},
}
});
}
}
}