Skip to content

Commit 16acb2a

Browse files
committed
Add config options to ignore constant comparisons and comparisons to constants to float_cmp
1 parent f20a076 commit 16acb2a

File tree

16 files changed

+579
-301
lines changed

16 files changed

+579
-301
lines changed

clippy_config/src/conf.rs

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

647676
/// Search for the configuration file.

clippy_lints/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
608608
allow_comparison_to_zero,
609609
ref allowed_prefixes,
610610
ref allow_renamed_params_for,
611+
float_cmp_ignore_named_constants,
612+
float_cmp_ignore_constant_comparisons,
611613

612614
blacklisted_names: _,
613615
cyclomatic_complexity_threshold: _,
@@ -1031,6 +1033,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
10311033
Box::new(operators::Operators::new(
10321034
verbose_bit_mask_threshold,
10331035
allow_comparison_to_zero,
1036+
float_cmp_ignore_named_constants,
1037+
float_cmp_ignore_constant_comparisons,
10341038
))
10351039
});
10361040
store.register_late_pass(|_| Box::<std_instead_of_core::StdReexports>::default());

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: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -836,10 +836,17 @@ declare_clippy_lint! {
836836
"explicit self-assignment"
837837
}
838838

839+
#[derive(Clone, Copy)]
840+
pub struct FloatCmpConfig {
841+
pub ignore_named_constants: bool,
842+
pub ignore_constant_comparisons: bool,
843+
}
844+
839845
pub struct Operators {
840846
arithmetic_context: numeric_arithmetic::Context,
841847
verbose_bit_mask_threshold: u64,
842848
modulo_arithmetic_allow_comparison_to_zero: bool,
849+
float_cmp_config: FloatCmpConfig,
843850
}
844851
impl_lint_pass!(Operators => [
845852
ABSURD_EXTREME_COMPARISONS,
@@ -870,11 +877,20 @@ impl_lint_pass!(Operators => [
870877
SELF_ASSIGNMENT,
871878
]);
872879
impl Operators {
873-
pub fn new(verbose_bit_mask_threshold: u64, modulo_arithmetic_allow_comparison_to_zero: bool) -> Self {
880+
pub fn new(
881+
verbose_bit_mask_threshold: u64,
882+
modulo_arithmetic_allow_comparison_to_zero: bool,
883+
ignore_named_constants: bool,
884+
ignore_constant_comparisons: bool,
885+
) -> Self {
874886
Self {
875887
arithmetic_context: numeric_arithmetic::Context::default(),
876888
verbose_bit_mask_threshold,
877889
modulo_arithmetic_allow_comparison_to_zero,
890+
float_cmp_config: FloatCmpConfig {
891+
ignore_named_constants,
892+
ignore_constant_comparisons,
893+
},
878894
}
879895
}
880896
}
@@ -903,7 +919,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
903919
float_equality_without_abs::check(cx, e, op.node, lhs, rhs);
904920
integer_division::check(cx, e, op.node, lhs, rhs);
905921
cmp_owned::check(cx, op.node, lhs, rhs);
906-
float_cmp::check(cx, e, op.node, lhs, rhs);
922+
float_cmp::check(cx, self.float_cmp_config, e, op.node, lhs, rhs);
907923
modulo_one::check(cx, e, op.node, rhs);
908924
modulo_arithmetic::check(
909925
cx,

clippy_utils/src/lib.rs

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

247+
/// Checks if the expression is path to either a constant or an associated constant.
248+
pub fn is_expr_named_const<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
249+
matches!(&e.kind, ExprKind::Path(p)
250+
if matches!(
251+
cx.qpath_res(p, e.hir_id),
252+
Res::Def(DefKind::Const | DefKind::AssocConst, _)
253+
)
254+
)
255+
}
256+
247257
/// Checks if a `Res` refers to a constructor of a `LangItem`
248258
/// For example, use this to check whether a function call or a pattern is `Some(..)`.
249259
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+

tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
4242
enum-variant-name-threshold
4343
enum-variant-size-threshold
4444
excessive-nesting-threshold
45+
float-cmp-ignore-constant-comparisons
46+
float-cmp-ignore-named-constants
4547
future-size-threshold
4648
ignore-interior-mutability
4749
large-error-threshold
@@ -126,6 +128,8 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
126128
enum-variant-name-threshold
127129
enum-variant-size-threshold
128130
excessive-nesting-threshold
131+
float-cmp-ignore-constant-comparisons
132+
float-cmp-ignore-named-constants
129133
future-size-threshold
130134
ignore-interior-mutability
131135
large-error-threshold
@@ -210,6 +214,8 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
210214
enum-variant-name-threshold
211215
enum-variant-size-threshold
212216
excessive-nesting-threshold
217+
float-cmp-ignore-constant-comparisons
218+
float-cmp-ignore-named-constants
213219
future-size-threshold
214220
ignore-interior-mutability
215221
large-error-threshold

0 commit comments

Comments
 (0)