| 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_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" | |
| } | |
| 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" | |
| } | |
| impl_lint_pass!(ManualFloatMethods => [MANUAL_IS_FINITE, MANUAL_IS_INFINITE]); | |
| #[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, | |
| ); | |
| }, | |
| } | |
| }); | |
| } | |
| } | |
| } |