diff --git a/CHANGELOG.md b/CHANGELOG.md index e2004c2931d..59719080e44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4941,6 +4941,8 @@ Released 2018-09-13 [`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten [`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed [`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check +[`manual_is_finite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_finite +[`manual_is_infinite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_infinite [`manual_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else [`manual_main_separator_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_main_separator_str [`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index ca97db04079..f8b8b94dd29 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -273,6 +273,8 @@ crate::manual_async_fn::MANUAL_ASYNC_FN_INFO, crate::manual_bits::MANUAL_BITS_INFO, crate::manual_clamp::MANUAL_CLAMP_INFO, + crate::manual_float_methods::MANUAL_IS_FINITE_INFO, + crate::manual_float_methods::MANUAL_IS_INFINITE_INFO, crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO, crate::manual_let_else::MANUAL_LET_ELSE_INFO, crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 00d46025caa..9abcbe01176 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -184,6 +184,7 @@ mod manual_async_fn; mod manual_bits; mod manual_clamp; +mod manual_float_methods; mod manual_is_ascii_check; mod manual_let_else; mod manual_main_separator_str; @@ -1073,6 +1074,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns)); store.register_early_pass(|| Box::new(visibility::Visibility)); store.register_late_pass(move |_| Box::new(tuple_array_conversions::TupleArrayConversions { msrv: msrv() })); + store.register_late_pass(|_| Box::new(manual_float_methods::ManualFloatMethods)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/manual_float_methods.rs b/clippy_lints/src/manual_float_methods.rs new file mode 100644 index 00000000000..abf1eb788dd --- /dev/null +++ b/clippy_lints/src/manual_float_methods.rs @@ -0,0 +1,106 @@ +use clippy_utils::{ + consts::constant, diagnostics::span_lint_and_sugg, is_from_proc_macro, path_to_local, source::snippet_opt, +}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass, 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 `x == ::INFINITY || x == ::NEG_INFINITY`. + /// + /// ### Why is this bad? + /// This should use the dedicated method instead, `is_infinite`. + /// + /// ### 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 `x != ::INFINITY && x != ::NEG_INFINITY`. + /// + /// ### Why is this bad? + /// This should use the dedicated method instead, `is_finite`. + /// + /// ### Example + /// ```rust + /// # let x = 1.0f32; + /// if x != f32::INFINITY && x != f32::NEG_INFINITY {} + /// ``` + /// Use instead: + /// ```rust + /// # let x = 1.0f32; + /// 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]); + +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) + && 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 + && let (operands, consts) = [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]) = consts + .into_iter() + .map(|i| constant(cx, cx.typeck_results(), i).and_then(|c| c.to_bits())) + .collect::>>() + .as_deref() + && path_to_local(first).is_some_and(|f| path_to_local(second).is_some_and(|s| f == s)) + && (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) + && !is_from_proc_macro(cx, expr) + { + let (msg, lint, sugg_fn) = match (kind.node, lhs_kind.node, rhs_kind.node) { + (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Eq) => { + ("manually checking if a float is infinite", MANUAL_IS_INFINITE, "is_infinite") + }, + (BinOpKind::And, BinOpKind::Ne, BinOpKind::Ne) => { + ("manually checking if a float is finite", MANUAL_IS_FINITE, "is_finite") + }, + _ => return, + }; + + span_lint_and_sugg( + cx, + lint, + expr.span, + msg, + "try", + format!("{local_snippet}.{sugg_fn}()"), + Applicability::MachineApplicable, + ); + } + } +} + +fn is_infinity(bits: u128) -> bool { + bits == 0x7f80_0000 || bits == 0x7ff0_0000_0000_0000 +} + +fn is_neg_infinity(bits: u128) -> bool { + bits == 0xff80_0000 || bits == 0xfff0_0000_0000_0000 +} diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 87d85d742ce..b9f8eefeb8e 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -190,6 +190,17 @@ pub fn partial_cmp(tcx: TyCtxt<'_>, cmp_type: Ty<'_>, left: &Self, right: &Self) } } + /// Returns the bit representation if `self` is a bool, integer, or float. + pub fn to_bits(&self) -> Option { + match self { + Constant::Int(int) => Some(*int), + Constant::F32(float) => Some(u128::from(float.to_bits())), + Constant::F64(float) => Some(u128::from(float.to_bits())), + Constant::Bool(bool) => Some(u128::from(*bool)), + _ => None, + } + } + /// Returns the integer value or `None` if `self` or `val_type` is not integer type. pub fn int_value(&self, cx: &LateContext<'_>, val_type: Ty<'_>) -> Option { if let Constant::Int(const_int) = *self { diff --git a/tests/ui/manual_float_methods.fixed b/tests/ui/manual_float_methods.fixed new file mode 100644 index 00000000000..87a23b5a75b --- /dev/null +++ b/tests/ui/manual_float_methods.fixed @@ -0,0 +1,37 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow(clippy::needless_if, unused)] +#![warn(clippy::manual_is_infinite, clippy::manual_is_finite)] + +#[macro_use] +extern crate proc_macros; + +const INFINITE: f32 = f32::INFINITY; +const NEG_INFINITE: f32 = f32::NEG_INFINITY; + +fn main() { + let x = 1.0f32; + if x.is_infinite() {} + if x.is_finite() {} + if x.is_infinite() {} + if x.is_finite() {} + let x = 1.0f64; + if x.is_infinite() {} + if x.is_finite() {} + // Don't lint + if x.is_infinite() {} + if x.is_finite() {} + // If they're doing it this way, they probably know what they're doing + if x.abs() < f64::INFINITY {} + external! { + let x = 1.0; + if x == f32::INFINITY || x == f32::NEG_INFINITY {} + if x != f32::INFINITY && x != f32::NEG_INFINITY {} + } + with_span! { + span + let x = 1.0; + if x == f32::INFINITY || x == f32::NEG_INFINITY {} + if x != f32::INFINITY && x != f32::NEG_INFINITY {} + } +} diff --git a/tests/ui/manual_float_methods.rs b/tests/ui/manual_float_methods.rs new file mode 100644 index 00000000000..9255bf93418 --- /dev/null +++ b/tests/ui/manual_float_methods.rs @@ -0,0 +1,37 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow(clippy::needless_if, unused)] +#![warn(clippy::manual_is_infinite, clippy::manual_is_finite)] + +#[macro_use] +extern crate proc_macros; + +const INFINITE: f32 = f32::INFINITY; +const NEG_INFINITE: f32 = f32::NEG_INFINITY; + +fn main() { + let x = 1.0f32; + if x == f32::INFINITY || x == f32::NEG_INFINITY {} + if x != f32::INFINITY && x != f32::NEG_INFINITY {} + if x == INFINITE || x == NEG_INFINITE {} + if x != INFINITE && x != NEG_INFINITE {} + let x = 1.0f64; + if x == f64::INFINITY || x == f64::NEG_INFINITY {} + if x != f64::INFINITY && x != f64::NEG_INFINITY {} + // Don't lint + if x.is_infinite() {} + if x.is_finite() {} + // If they're doing it this way, they probably know what they're doing + if x.abs() < f64::INFINITY {} + external! { + let x = 1.0; + if x == f32::INFINITY || x == f32::NEG_INFINITY {} + if x != f32::INFINITY && x != f32::NEG_INFINITY {} + } + with_span! { + span + let x = 1.0; + if x == f32::INFINITY || x == f32::NEG_INFINITY {} + if x != f32::INFINITY && x != f32::NEG_INFINITY {} + } +} diff --git a/tests/ui/manual_float_methods.stderr b/tests/ui/manual_float_methods.stderr new file mode 100644 index 00000000000..00227593a9e --- /dev/null +++ b/tests/ui/manual_float_methods.stderr @@ -0,0 +1,42 @@ +error: manually checking if a float is infinite + --> $DIR/manual_float_methods.rs:14:8 + | +LL | if x == f32::INFINITY || x == f32::NEG_INFINITY {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_infinite()` + | + = note: `-D clippy::manual-is-infinite` implied by `-D warnings` + +error: manually checking if a float is finite + --> $DIR/manual_float_methods.rs:15:8 + | +LL | if x != f32::INFINITY && x != f32::NEG_INFINITY {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_finite()` + | + = note: `-D clippy::manual-is-finite` implied by `-D warnings` + +error: manually checking if a float is infinite + --> $DIR/manual_float_methods.rs:16:8 + | +LL | if x == INFINITE || x == NEG_INFINITE {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_infinite()` + +error: manually checking if a float is finite + --> $DIR/manual_float_methods.rs:17:8 + | +LL | if x != INFINITE && x != NEG_INFINITE {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_finite()` + +error: manually checking if a float is infinite + --> $DIR/manual_float_methods.rs:19:8 + | +LL | if x == f64::INFINITY || x == f64::NEG_INFINITY {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_infinite()` + +error: manually checking if a float is finite + --> $DIR/manual_float_methods.rs:20:8 + | +LL | if x != f64::INFINITY && x != f64::NEG_INFINITY {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_finite()` + +error: aborting due to 6 previous errors +