Skip to content

Commit 680c681

Browse files
committed
Added a lint which corrects expressions like (a - b) < f32::EPSILON
1 parent 27ae4d3 commit 680c681

File tree

6 files changed

+226
-0
lines changed

6 files changed

+226
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1498,6 +1498,7 @@ Released 2018-09-13
14981498
[`float_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_arithmetic
14991499
[`float_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
15001500
[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
1501+
[`float_equality_without_abs`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_equality_without_abs
15011502
[`fn_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_address_comparisons
15021503
[`fn_params_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools
15031504
[`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
use crate::utils::{match_qpath, snippet, span_lint_and_sugg};
2+
use if_chain::if_chain;
3+
use rustc_errors::Applicability;
4+
use rustc_hir::{BinOpKind, Expr, ExprKind};
5+
use rustc_lint::{LateContext, LateLintPass};
6+
use rustc_middle::ty;
7+
use rustc_session::{declare_lint_pass, declare_tool_lint};
8+
use rustc_span::source_map::Spanned;
9+
10+
declare_clippy_lint! {
11+
/// **What it does:** Checks for statements of the form `(a - b) < f32::EPSILON` or
12+
/// `(a - b) < f64::EPSILON`. Notes the missing `.abs()`.
13+
///
14+
/// **Why is this bad?** The code without `.abs()` is more likely to have a bug.
15+
///
16+
/// **Known problems:** If the user can ensure that b is larger than a, the `.abs()` is
17+
/// technically unneccessary. However, it will make the code more robust and doesn't have any
18+
/// large performance implications. If the abs call was deliberately left out for performance
19+
/// reasons, it is probably better to state this explicitly in the code, which then can be done
20+
/// with an allow.
21+
///
22+
/// **Example:**
23+
///
24+
/// ```rust
25+
/// pub fn is_roughly_equal(a: f32, b: f32) -> bool {
26+
/// (a - b) < f32::EPSILON
27+
/// }
28+
/// ```
29+
/// Use instead:
30+
/// ```rust
31+
/// pub fn is_roughly_equal(a: f32, b: f32) -> bool {
32+
/// (a - b).abs() < f32::EPSILON
33+
/// }
34+
/// ```
35+
pub FLOAT_EQUALITY_WITHOUT_ABS,
36+
correctness,
37+
"float equality check without `.abs()`"
38+
}
39+
40+
declare_lint_pass!(FloatEqualityWithoutAbs => [FLOAT_EQUALITY_WITHOUT_ABS]);
41+
42+
impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs {
43+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
44+
let lhs;
45+
let rhs;
46+
47+
// check if expr is a binary expression with a lt or gt operator
48+
if let ExprKind::Binary(op, ref left, ref right) = expr.kind {
49+
match op.node {
50+
BinOpKind::Lt => {
51+
lhs = left;
52+
rhs = right;
53+
},
54+
BinOpKind::Gt => {
55+
lhs = right;
56+
rhs = left;
57+
},
58+
_ => return,
59+
};
60+
} else {
61+
return;
62+
}
63+
64+
if_chain! {
65+
66+
// left hand side is a substraction
67+
if let ExprKind::Binary(
68+
Spanned {
69+
node: BinOpKind::Sub,
70+
..
71+
},
72+
val_l,
73+
val_r,
74+
) = lhs.kind;
75+
76+
// right hand side matches either f32::EPSILON or f64::EPSILON
77+
if let ExprKind::Path(ref epsilon_path) = rhs.kind;
78+
if match_qpath(epsilon_path, &["f32", "EPSILON"]) || match_qpath(epsilon_path, &["f64", "EPSILON"]);
79+
80+
// values of the substractions on the left hand side are of the type float
81+
let t_val_l = cx.typeck_results().expr_ty(val_l);
82+
let t_val_r = cx.typeck_results().expr_ty(val_r);
83+
if let ty::Float(_) = t_val_l.kind;
84+
if let ty::Float(_) = t_val_r.kind;
85+
86+
then {
87+
// get the snippet string
88+
let lhs_string = snippet(
89+
cx,
90+
lhs.span,
91+
"(...)",
92+
);
93+
// format the suggestion
94+
let suggestion = if lhs_string.starts_with('(') {
95+
format!("{}.abs()", lhs_string)
96+
} else {
97+
format!("({}).abs()", lhs_string)
98+
};
99+
// spans the lint
100+
span_lint_and_sugg(
101+
cx,
102+
FLOAT_EQUALITY_WITHOUT_ABS,
103+
expr.span,
104+
"float equality check without `.abs()`",
105+
"add `.abs()`",
106+
suggestion,
107+
Applicability::MaybeIncorrect,
108+
);
109+
}
110+
}
111+
}
112+
}

clippy_lints/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ mod excessive_bools;
193193
mod exit;
194194
mod explicit_write;
195195
mod fallible_impl_from;
196+
mod float_equality_without_abs;
196197
mod float_literal;
197198
mod floating_point_arithmetic;
198199
mod format;
@@ -549,6 +550,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
549550
&exit::EXIT,
550551
&explicit_write::EXPLICIT_WRITE,
551552
&fallible_impl_from::FALLIBLE_IMPL_FROM,
553+
&float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS,
552554
&float_literal::EXCESSIVE_PRECISION,
553555
&float_literal::LOSSY_FLOAT_LITERAL,
554556
&floating_point_arithmetic::IMPRECISE_FLOPS,
@@ -1093,6 +1095,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10931095
store.register_late_pass(|| box stable_sort_primitive::StableSortPrimitive);
10941096
store.register_late_pass(|| box repeat_once::RepeatOnce);
10951097
store.register_late_pass(|| box self_assignment::SelfAssignment);
1098+
store.register_late_pass(|| box float_equality_without_abs::FloatEqualityWithoutAbs);
10961099

10971100
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
10981101
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1268,6 +1271,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
12681271
LintId::of(&eval_order_dependence::DIVERGING_SUB_EXPRESSION),
12691272
LintId::of(&eval_order_dependence::EVAL_ORDER_DEPENDENCE),
12701273
LintId::of(&explicit_write::EXPLICIT_WRITE),
1274+
LintId::of(&float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
12711275
LintId::of(&float_literal::EXCESSIVE_PRECISION),
12721276
LintId::of(&format::USELESS_FORMAT),
12731277
LintId::of(&formatting::POSSIBLE_MISSING_COMMA),
@@ -1686,6 +1690,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16861690
LintId::of(&enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
16871691
LintId::of(&eq_op::EQ_OP),
16881692
LintId::of(&erasing_op::ERASING_OP),
1693+
LintId::of(&float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
16891694
LintId::of(&formatting::POSSIBLE_MISSING_COMMA),
16901695
LintId::of(&functions::NOT_UNSAFE_PTR_ARG_DEREF),
16911696
LintId::of(&if_let_mutex::IF_LET_MUTEX),

src/lintlist/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
661661
deprecation: None,
662662
module: "misc",
663663
},
664+
Lint {
665+
name: "float_equality_without_abs",
666+
group: "correctness",
667+
desc: "float equality check without `.abs()`",
668+
deprecation: None,
669+
module: "float_equality_without_abs",
670+
},
664671
Lint {
665672
name: "fn_address_comparisons",
666673
group: "correctness",
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#![warn(clippy::float_equality_without_abs)]
2+
3+
pub fn is_roughly_equal(a: f32, b: f32) -> bool {
4+
(a - b) < f32::EPSILON
5+
}
6+
7+
pub fn main() {
8+
// all errors
9+
is_roughly_equal(1.0, 2.0);
10+
let a = 0.05;
11+
let b = 0.0500001;
12+
13+
let _ = (a - b) < f32::EPSILON;
14+
let _ = a - b < f32::EPSILON;
15+
let _ = a - b.abs() < f32::EPSILON;
16+
let _ = (a as f64 - b as f64) < f64::EPSILON;
17+
let _ = 1.0 - 2.0 < f32::EPSILON;
18+
19+
let _ = f32::EPSILON > (a - b);
20+
let _ = f32::EPSILON > a - b;
21+
let _ = f32::EPSILON > a - b.abs();
22+
let _ = f64::EPSILON > (a as f64 - b as f64);
23+
let _ = f32::EPSILON > 1.0 - 2.0;
24+
25+
// those are correct
26+
let _ = (a - b).abs() < f32::EPSILON;
27+
let _ = (a as f64 - b as f64).abs() < f64::EPSILON;
28+
29+
let _ = f32::EPSILON > (a - b).abs();
30+
let _ = f64::EPSILON > (a as f64 - b as f64).abs();
31+
}
+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
error: float equality check without `.abs()`
2+
--> $DIR/float_equality_without_abs.rs:4:5
3+
|
4+
LL | (a - b) < f32::EPSILON
5+
| ^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a - b).abs()`
6+
|
7+
= note: `-D clippy::float-equality-without-abs` implied by `-D warnings`
8+
9+
error: float equality check without `.abs()`
10+
--> $DIR/float_equality_without_abs.rs:13:13
11+
|
12+
LL | let _ = (a - b) < f32::EPSILON;
13+
| ^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a - b).abs()`
14+
15+
error: float equality check without `.abs()`
16+
--> $DIR/float_equality_without_abs.rs:14:13
17+
|
18+
LL | let _ = a - b < f32::EPSILON;
19+
| ^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a - b).abs()`
20+
21+
error: float equality check without `.abs()`
22+
--> $DIR/float_equality_without_abs.rs:15:13
23+
|
24+
LL | let _ = a - b.abs() < f32::EPSILON;
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a - b.abs()).abs()`
26+
27+
error: float equality check without `.abs()`
28+
--> $DIR/float_equality_without_abs.rs:16:13
29+
|
30+
LL | let _ = (a as f64 - b as f64) < f64::EPSILON;
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a as f64 - b as f64).abs()`
32+
33+
error: float equality check without `.abs()`
34+
--> $DIR/float_equality_without_abs.rs:17:13
35+
|
36+
LL | let _ = 1.0 - 2.0 < f32::EPSILON;
37+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(1.0 - 2.0).abs()`
38+
39+
error: float equality check without `.abs()`
40+
--> $DIR/float_equality_without_abs.rs:19:13
41+
|
42+
LL | let _ = f32::EPSILON > (a - b);
43+
| ^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a - b).abs()`
44+
45+
error: float equality check without `.abs()`
46+
--> $DIR/float_equality_without_abs.rs:20:13
47+
|
48+
LL | let _ = f32::EPSILON > a - b;
49+
| ^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a - b).abs()`
50+
51+
error: float equality check without `.abs()`
52+
--> $DIR/float_equality_without_abs.rs:21:13
53+
|
54+
LL | let _ = f32::EPSILON > a - b.abs();
55+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a - b.abs()).abs()`
56+
57+
error: float equality check without `.abs()`
58+
--> $DIR/float_equality_without_abs.rs:22:13
59+
|
60+
LL | let _ = f64::EPSILON > (a as f64 - b as f64);
61+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a as f64 - b as f64).abs()`
62+
63+
error: float equality check without `.abs()`
64+
--> $DIR/float_equality_without_abs.rs:23:13
65+
|
66+
LL | let _ = f32::EPSILON > 1.0 - 2.0;
67+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(1.0 - 2.0).abs()`
68+
69+
error: aborting due to 11 previous errors
70+

0 commit comments

Comments
 (0)