Skip to content

Commit 23cd0ad

Browse files
committed
Add config options to ignore constant comparisons and comparisons to constants to float_cmp
1 parent 86bb771 commit 23cd0ad

File tree

15 files changed

+569
-300
lines changed

15 files changed

+569
-300
lines changed

clippy_config/src/conf.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,35 @@ define_Conf! {
645645
///
646646
/// Whether to also emit warnings for unsafe blocks with metavariable expansions in **private** macros.
647647
(warn_unsafe_macro_metavars_in_private_macros: bool = false),
648+
/// Lint: FLOAT_CMP
649+
///
650+
/// Whether to ignore comparisons to a named constnat
651+
///
652+
/// #### Example
653+
/// ```no_run
654+
/// const VALUE: f64 = 1.0;
655+
/// fn is_value(x: f64) -> bool {
656+
/// // Will warn if the config is `false`
657+
/// x == VALUE
658+
/// }
659+
/// ```
660+
(float_cmp_ignore_named_constants: bool = true),
661+
/// Lint: FLOAT_CMP
662+
///
663+
/// Whether to ignore comparisons which have a constant result.
664+
///
665+
/// #### Example
666+
/// ```no_run
667+
/// const fn f(x: f64) -> f64 {
668+
/// todo!()
669+
/// }
670+
///
671+
/// // Will warn if the config is `false`
672+
/// if f(1.0) == f(2.0) {
673+
/// // ...
674+
/// }
675+
/// ```
676+
(float_cmp_ignore_constant_comparisons: bool = true),
648677
}
649678

650679
/// Search for the configuration file.

clippy_lints/src/operators/float_cmp.rs

Lines changed: 42 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,54 @@
1-
use clippy_utils::consts::{constant_with_source, Constant};
1+
use clippy_utils::consts::{constant, Constant};
22
use clippy_utils::diagnostics::span_lint_and_then;
3-
use clippy_utils::get_item_name;
43
use clippy_utils::sugg::Sugg;
4+
use clippy_utils::visitors::is_const_evaluatable;
5+
use clippy_utils::{get_item_name, is_expr_named_const, peel_hir_expr_while};
56
use rustc_errors::Applicability;
6-
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
7+
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, UnOp};
78
use rustc_lint::LateContext;
89
use rustc_middle::ty;
910

10-
use super::{FLOAT_CMP, FLOAT_CMP_CONST};
11+
use super::{FloatCmpConfig, FLOAT_CMP};
1112

1213
pub(crate) fn check<'tcx>(
1314
cx: &LateContext<'tcx>,
15+
config: FloatCmpConfig,
1416
expr: &'tcx Expr<'_>,
1517
op: BinOpKind,
1618
left: &'tcx Expr<'_>,
1719
right: &'tcx Expr<'_>,
1820
) {
19-
if (op == BinOpKind::Eq || op == BinOpKind::Ne) && is_float(cx, left) {
20-
let left_is_local = match constant_with_source(cx, cx.typeck_results(), left) {
21-
Some((c, s)) if !is_allowed(&c) => s.is_local(),
22-
Some(_) => return,
23-
None => true,
24-
};
25-
let right_is_local = match constant_with_source(cx, cx.typeck_results(), right) {
26-
Some((c, s)) if !is_allowed(&c) => s.is_local(),
27-
Some(_) => return,
28-
None => true,
29-
};
30-
21+
if (op == BinOpKind::Eq || op == BinOpKind::Ne)
22+
&& is_float(cx, left)
3123
// Allow comparing the results of signum()
32-
if is_signum(cx, left) && is_signum(cx, right) {
24+
&& !(is_signum(cx, left) && is_signum(cx, right))
25+
{
26+
let left_c = constant(cx, cx.typeck_results(), left);
27+
let is_left_const = left_c.is_some();
28+
if left_c.is_some_and(|c| is_allowed(&c)) {
29+
return;
30+
}
31+
let right_c = constant(cx, cx.typeck_results(), right);
32+
let is_right_const = right_c.is_some();
33+
if right_c.is_some_and(|c| is_allowed(&c)) {
34+
return;
35+
}
36+
37+
if config.ignore_constant_comparisons
38+
&& (is_left_const || is_const_evaluatable(cx, left))
39+
&& (is_right_const || is_const_evaluatable(cx, right))
40+
{
41+
return;
42+
}
43+
44+
let peel_expr = |e: &'tcx Expr<'tcx>| match e.kind {
45+
ExprKind::Cast(e, _) | ExprKind::AddrOf(BorrowKind::Ref, _, e) => Some(e),
46+
_ => None,
47+
};
48+
if config.ignore_named_constants
49+
&& (is_expr_named_const(cx, peel_hir_expr_while(left, peel_expr))
50+
|| is_expr_named_const(cx, peel_hir_expr_while(right, peel_expr)))
51+
{
3352
return;
3453
}
3554

@@ -40,8 +59,12 @@ pub(crate) fn check<'tcx>(
4059
}
4160
}
4261
let is_comparing_arrays = is_array(cx, left) || is_array(cx, right);
43-
let (lint, msg) = get_lint_and_message(left_is_local && right_is_local, is_comparing_arrays);
44-
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
62+
let msg = if is_comparing_arrays {
63+
"strict comparison of `f32` or `f64` arrays"
64+
} else {
65+
"strict comparison of `f32` or `f64`"
66+
};
67+
span_lint_and_then(cx, FLOAT_CMP, expr.span, msg, |diag| {
4568
let lhs = Sugg::hir(cx, left, "..");
4669
let rhs = Sugg::hir(cx, right, "..");
4770

@@ -61,28 +84,6 @@ pub(crate) fn check<'tcx>(
6184
}
6285
}
6386

64-
fn get_lint_and_message(is_local: bool, is_comparing_arrays: bool) -> (&'static rustc_lint::Lint, &'static str) {
65-
if is_local {
66-
(
67-
FLOAT_CMP,
68-
if is_comparing_arrays {
69-
"strict comparison of `f32` or `f64` arrays"
70-
} else {
71-
"strict comparison of `f32` or `f64`"
72-
},
73-
)
74-
} else {
75-
(
76-
FLOAT_CMP_CONST,
77-
if is_comparing_arrays {
78-
"strict comparison of `f32` or `f64` constant arrays"
79-
} else {
80-
"strict comparison of `f32` or `f64` constant"
81-
},
82-
)
83-
}
84-
}
85-
8687
fn is_allowed(val: &Constant<'_>) -> bool {
8788
match val {
8889
// FIXME(f16_f128): add when equality check is available on all platforms

clippy_lints/src/operators/mod.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -837,17 +837,28 @@ declare_clippy_lint! {
837837
"explicit self-assignment"
838838
}
839839

840+
#[derive(Clone, Copy)]
841+
struct FloatCmpConfig {
842+
ignore_named_constants: bool,
843+
ignore_constant_comparisons: bool,
844+
}
845+
840846
pub struct Operators {
841847
arithmetic_context: numeric_arithmetic::Context,
842848
verbose_bit_mask_threshold: u64,
843849
modulo_arithmetic_allow_comparison_to_zero: bool,
850+
float_cmp_config: FloatCmpConfig,
844851
}
845852
impl Operators {
846853
pub fn new(conf: &'static Conf) -> Self {
847854
Self {
848855
arithmetic_context: numeric_arithmetic::Context::default(),
849856
verbose_bit_mask_threshold: conf.verbose_bit_mask_threshold,
850857
modulo_arithmetic_allow_comparison_to_zero: conf.allow_comparison_to_zero,
858+
float_cmp_config: FloatCmpConfig {
859+
ignore_named_constants: conf.float_cmp_ignore_named_constants,
860+
ignore_constant_comparisons: conf.float_cmp_ignore_constant_comparisons,
861+
},
851862
}
852863
}
853864
}
@@ -906,7 +917,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
906917
float_equality_without_abs::check(cx, e, op.node, lhs, rhs);
907918
integer_division::check(cx, e, op.node, lhs, rhs);
908919
cmp_owned::check(cx, op.node, lhs, rhs);
909-
float_cmp::check(cx, e, op.node, lhs, rhs);
920+
float_cmp::check(cx, self.float_cmp_config, e, op.node, lhs, rhs);
910921
modulo_one::check(cx, e, op.node, rhs);
911922
modulo_arithmetic::check(
912923
cx,

clippy_utils/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,16 @@ pub fn is_inside_always_const_context(tcx: TyCtxt<'_>, hir_id: HirId) -> bool {
245245
}
246246
}
247247

248+
/// Checks if the expression is path to either a constant or an associated constant.
249+
pub fn is_expr_named_const<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
250+
matches!(&e.kind, ExprKind::Path(p)
251+
if matches!(
252+
cx.qpath_res(p, e.hir_id),
253+
Res::Def(DefKind::Const | DefKind::AssocConst, _)
254+
)
255+
)
256+
}
257+
248258
/// Checks if a `Res` refers to a constructor of a `LangItem`
249259
/// For example, use this to check whether a function call or a pattern is `Some(..)`.
250260
pub fn is_res_lang_ctor(cx: &LateContext<'_>, res: Res, lang_item: LangItem) -> bool {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
float-cmp-ignore-constant-comparisons = false
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//@no-rustfix
2+
3+
#![deny(clippy::float_cmp)]
4+
5+
fn main() {
6+
{
7+
const C: f64 = 1.0;
8+
fn f(x: f64) {
9+
let _ = x == C;
10+
}
11+
}
12+
{
13+
const fn f(x: f64) -> f64 {
14+
todo!()
15+
}
16+
let _ = f(1.0) == f(2.0);
17+
}
18+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: strict comparison of `f32` or `f64`
2+
--> tests/ui-toml/float_cmp_constant_comparisons/test.rs:16:17
3+
|
4+
LL | let _ = f(1.0) == f(2.0);
5+
| ^^^^^^^^^^^^^^^^ help: consider comparing them within some margin of error: `(f(1.0) - f(2.0)).abs() < error_margin`
6+
|
7+
note: the lint level is defined here
8+
--> tests/ui-toml/float_cmp_constant_comparisons/test.rs:3:9
9+
|
10+
LL | #![deny(clippy::float_cmp)]
11+
| ^^^^^^^^^^^^^^^^^
12+
13+
error: aborting due to 1 previous error
14+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
float-cmp-ignore-named-constants = false
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//@no-rustfix
2+
3+
#![deny(clippy::float_cmp)]
4+
5+
fn main() {
6+
{
7+
const C: f64 = 1.0;
8+
fn f(x: f64) {
9+
let _ = x == C;
10+
}
11+
}
12+
{
13+
const fn f(x: f64) -> f64 {
14+
todo!()
15+
}
16+
let _ = f(1.0) == f(2.0);
17+
}
18+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: strict comparison of `f32` or `f64`
2+
--> tests/ui-toml/float_cmp_named_constants/test.rs:9:21
3+
|
4+
LL | let _ = x == C;
5+
| ^^^^^^ help: consider comparing them within some margin of error: `(x - C).abs() < error_margin`
6+
|
7+
note: the lint level is defined here
8+
--> tests/ui-toml/float_cmp_named_constants/test.rs:3:9
9+
|
10+
LL | #![deny(clippy::float_cmp)]
11+
| ^^^^^^^^^^^^^^^^^
12+
13+
error: aborting due to 1 previous error
14+

0 commit comments

Comments
 (0)