172 lines
6.6 KiB
Rust
172 lines
6.6 KiB
Rust
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_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"
|
|
}
|
|
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().declared(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::<Vec<&Expr<'_>>, _>(|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::<Option<Vec<_>>>()
|
|
.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))
|
|
&& 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,
|
|
};
|
|
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,
|
|
);
|
|
},
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|