use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_opt; use clippy_utils::{is_from_proc_macro, path_to_local}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Constness, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass, Lint, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { /// ### What it does /// Checks for manual `is_infinite` reimplementations /// (i.e., `x == ::INFINITY || x == ::NEG_INFINITY`). /// /// ### Why is this bad? /// The method `is_infinite` is shorter and more readable. /// /// ### Example /// ```rust /// # let x = 1.0f32; /// if x == f32::INFINITY || x == f32::NEG_INFINITY {} /// ``` /// Use instead: /// ```rust /// # let x = 1.0f32; /// if x.is_infinite() {} /// ``` #[clippy::version = "1.72.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 != ::INFINITY && x != ::NEG_INFINITY`). /// /// ### Why is this bad? /// The method `is_finite` is shorter and more readable. /// /// ### Example /// ```rust /// # let x = 1.0f32; /// if x != f32::INFINITY && x != f32::NEG_INFINITY {} /// if x.abs() < f32::INFINITY {} /// ``` /// Use instead: /// ```rust /// # let x = 1.0f32; /// if x.is_finite() {} /// if x.is_finite() {} /// ``` #[clippy::version = "1.72.0"] pub MANUAL_IS_FINITE, style, "use dedicated method to check if a float is finite" } declare_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", } } } impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if !in_external_macro(cx.sess(), expr.span) && ( matches!(cx.tcx.constness(cx.tcx.hir().enclosing_body_owner(expr.hir_id)), Constness::NotConst) || cx.tcx.features().active(sym!(const_float_classify)) ) && 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 (operands, constants) = [lhs_lhs, lhs_rhs, rhs_lhs, rhs_rhs] .into_iter() .partition::>, _>(|i| path_to_local(i).is_some()) && let [first, second] = &*operands && let Some([const_1, const_2]) = constants .into_iter() .map(|i| constant(cx, cx.typeck_results(), i)) .collect::>>() .as_deref() && path_to_local(first).is_some_and(|f| path_to_local(second).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 && (is_infinity(const_1) && is_neg_infinity(const_2) || is_neg_infinity(const_1) && is_infinity(const_2)) && !is_from_proc_macro(cx, expr) && let Some(local_snippet) = snippet_opt(cx, first.span) { 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, }; 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, ); }, } }, ); } } } fn is_infinity(constant: &Constant<'_>) -> bool { match constant { Constant::F32(float) => *float == f32::INFINITY, Constant::F64(float) => *float == f64::INFINITY, _ => false, } } fn is_neg_infinity(constant: &Constant<'_>) -> bool { match constant { Constant::F32(float) => *float == f32::NEG_INFINITY, Constant::F64(float) => *float == f64::NEG_INFINITY, _ => false, } }