manual_float_methods

This commit is contained in:
Catherine 2023-06-30 11:31:08 -05:00
parent dd8e44c5a2
commit f12edfdb53
8 changed files with 239 additions and 0 deletions

View File

@ -4941,6 +4941,8 @@ Released 2018-09-13
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten [`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_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_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_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_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 [`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map

View File

@ -273,6 +273,8 @@
crate::manual_async_fn::MANUAL_ASYNC_FN_INFO, crate::manual_async_fn::MANUAL_ASYNC_FN_INFO,
crate::manual_bits::MANUAL_BITS_INFO, crate::manual_bits::MANUAL_BITS_INFO,
crate::manual_clamp::MANUAL_CLAMP_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_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
crate::manual_let_else::MANUAL_LET_ELSE_INFO, crate::manual_let_else::MANUAL_LET_ELSE_INFO,
crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO, crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO,

View File

@ -184,6 +184,7 @@
mod manual_async_fn; mod manual_async_fn;
mod manual_bits; mod manual_bits;
mod manual_clamp; mod manual_clamp;
mod manual_float_methods;
mod manual_is_ascii_check; mod manual_is_ascii_check;
mod manual_let_else; mod manual_let_else;
mod manual_main_separator_str; 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_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns));
store.register_early_pass(|| Box::new(visibility::Visibility)); 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(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` // add lints here, do not remove this comment, it's used in `new_lint`
} }

View File

@ -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 == <float>::INFINITY || x == <float>::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 != <float>::INFINITY && x != <float>::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::<Vec<&Expr<'_>>, _>(|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::<Option<Vec<_>>>()
.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
}

View File

@ -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<u128> {
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. /// 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<FullInt> { pub fn int_value(&self, cx: &LateContext<'_>, val_type: Ty<'_>) -> Option<FullInt> {
if let Constant::Int(const_int) = *self { if let Constant::Int(const_int) = *self {

View File

@ -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 {}
}
}

View File

@ -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 {}
}
}

View File

@ -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