-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added a lint which corrects expressions like (a - b) < f32::EPSILON, according to #5819 #5952
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
use crate::utils::{match_qpath, paths, snippet, span_lint_and_sugg}; | ||
use if_chain::if_chain; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{BinOpKind, Expr, ExprKind}; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_middle::ty; | ||
use rustc_session::{declare_lint_pass, declare_tool_lint}; | ||
use rustc_span::source_map::Spanned; | ||
|
||
declare_clippy_lint! { | ||
/// **What it does:** Checks for statements of the form `(a - b) < f32::EPSILON` or | ||
/// `(a - b) < f64::EPSILON`. Notes the missing `.abs()`. | ||
/// | ||
/// **Why is this bad?** The code without `.abs()` is more likely to have a bug. | ||
/// | ||
/// **Known problems:** If the user can ensure that b is larger than a, the `.abs()` is | ||
/// technically unneccessary. However, it will make the code more robust and doesn't have any | ||
/// large performance implications. If the abs call was deliberately left out for performance | ||
/// reasons, it is probably better to state this explicitly in the code, which then can be done | ||
/// with an allow. | ||
/// | ||
/// **Example:** | ||
/// | ||
/// ```rust | ||
/// pub fn is_roughly_equal(a: f32, b: f32) -> bool { | ||
/// (a - b) < f32::EPSILON | ||
/// } | ||
/// ``` | ||
/// Use instead: | ||
/// ```rust | ||
/// pub fn is_roughly_equal(a: f32, b: f32) -> bool { | ||
/// (a - b).abs() < f32::EPSILON | ||
/// } | ||
/// ``` | ||
pub FLOAT_EQUALITY_WITHOUT_ABS, | ||
correctness, | ||
"float equality check without `.abs()`" | ||
} | ||
|
||
declare_lint_pass!(FloatEqualityWithoutAbs => [FLOAT_EQUALITY_WITHOUT_ABS]); | ||
|
||
impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs { | ||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | ||
let lhs; | ||
let rhs; | ||
|
||
// check if expr is a binary expression with a lt or gt operator | ||
if let ExprKind::Binary(op, ref left, ref right) = expr.kind { | ||
match op.node { | ||
BinOpKind::Lt => { | ||
lhs = left; | ||
rhs = right; | ||
}, | ||
BinOpKind::Gt => { | ||
lhs = right; | ||
rhs = left; | ||
}, | ||
_ => return, | ||
}; | ||
} else { | ||
return; | ||
} | ||
|
||
if_chain! { | ||
|
||
// left hand side is a substraction | ||
if let ExprKind::Binary( | ||
Spanned { | ||
node: BinOpKind::Sub, | ||
.. | ||
}, | ||
val_l, | ||
val_r, | ||
) = lhs.kind; | ||
|
||
// right hand side matches either f32::EPSILON or f64::EPSILON | ||
if let ExprKind::Path(ref epsilon_path) = rhs.kind; | ||
if match_qpath(epsilon_path, &paths::F32_EPSILON) || match_qpath(epsilon_path, &paths::F64_EPSILON); | ||
|
||
// values of the substractions on the left hand side are of the type float | ||
let t_val_l = cx.typeck_results().expr_ty(val_l); | ||
let t_val_r = cx.typeck_results().expr_ty(val_r); | ||
if let ty::Float(_) = t_val_l.kind; | ||
if let ty::Float(_) = t_val_r.kind; | ||
|
||
then { | ||
// get the snippet string | ||
let lhs_string = snippet( | ||
cx, | ||
lhs.span, | ||
"(...)", | ||
); | ||
// format the suggestion | ||
let suggestion = if lhs_string.starts_with('(') { | ||
format!("{}.abs()", lhs_string) | ||
} else { | ||
format!("({}).abs()", lhs_string) | ||
}; | ||
Comment on lines
+87
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @1c3t3a If you want to further improve this code, you can use |
||
// spans the lint | ||
span_lint_and_sugg( | ||
cx, | ||
FLOAT_EQUALITY_WITHOUT_ABS, | ||
expr.span, | ||
"float equality check without `.abs()`", | ||
"add `.abs()`", | ||
suggestion, | ||
Applicability::MaybeIncorrect, | ||
); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
#![warn(clippy::float_equality_without_abs)] | ||
|
||
pub fn is_roughly_equal(a: f32, b: f32) -> bool { | ||
(a - b) < f32::EPSILON | ||
} | ||
|
||
pub fn main() { | ||
// all errors | ||
is_roughly_equal(1.0, 2.0); | ||
let a = 0.05; | ||
let b = 0.0500001; | ||
|
||
let _ = (a - b) < f32::EPSILON; | ||
let _ = a - b < f32::EPSILON; | ||
let _ = a - b.abs() < f32::EPSILON; | ||
let _ = (a as f64 - b as f64) < f64::EPSILON; | ||
let _ = 1.0 - 2.0 < f32::EPSILON; | ||
|
||
let _ = f32::EPSILON > (a - b); | ||
let _ = f32::EPSILON > a - b; | ||
let _ = f32::EPSILON > a - b.abs(); | ||
let _ = f64::EPSILON > (a as f64 - b as f64); | ||
let _ = f32::EPSILON > 1.0 - 2.0; | ||
|
||
// those are correct | ||
let _ = (a - b).abs() < f32::EPSILON; | ||
let _ = (a as f64 - b as f64).abs() < f64::EPSILON; | ||
|
||
let _ = f32::EPSILON > (a - b).abs(); | ||
let _ = f64::EPSILON > (a as f64 - b as f64).abs(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
error: float equality check without `.abs()` | ||
--> $DIR/float_equality_without_abs.rs:4:5 | ||
| | ||
LL | (a - b) < f32::EPSILON | ||
| ^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a - b).abs()` | ||
| | ||
= note: `-D clippy::float-equality-without-abs` implied by `-D warnings` | ||
|
||
error: float equality check without `.abs()` | ||
--> $DIR/float_equality_without_abs.rs:13:13 | ||
| | ||
LL | let _ = (a - b) < f32::EPSILON; | ||
| ^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a - b).abs()` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This suggestion doesn't work. If applied, this will transform the code like this: let _ = (a - b) < f32::EPSILON; // before
let _ = (a - b).abs(); // after A better way to do this would be to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh you're right! Thank you @flip1995 and sorry that I didn't see that earlier! I've corrected it on my own fork. Should I open a new PR for this fix? I've also started to use let suggestion = format!("{}.abs()", sugg::Sugg::hir(cx, &lhs, "..").maybe_par()); always brought up doubled parentheses. E.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The linked commit looks good to me. Yes please open a new PR about this! |
||
|
||
error: float equality check without `.abs()` | ||
--> $DIR/float_equality_without_abs.rs:14:13 | ||
| | ||
LL | let _ = a - b < f32::EPSILON; | ||
| ^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a - b).abs()` | ||
|
||
error: float equality check without `.abs()` | ||
--> $DIR/float_equality_without_abs.rs:15:13 | ||
| | ||
LL | let _ = a - b.abs() < f32::EPSILON; | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a - b.abs()).abs()` | ||
|
||
error: float equality check without `.abs()` | ||
--> $DIR/float_equality_without_abs.rs:16:13 | ||
| | ||
LL | let _ = (a as f64 - b as f64) < f64::EPSILON; | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a as f64 - b as f64).abs()` | ||
|
||
error: float equality check without `.abs()` | ||
--> $DIR/float_equality_without_abs.rs:17:13 | ||
| | ||
LL | let _ = 1.0 - 2.0 < f32::EPSILON; | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(1.0 - 2.0).abs()` | ||
|
||
error: float equality check without `.abs()` | ||
--> $DIR/float_equality_without_abs.rs:19:13 | ||
| | ||
LL | let _ = f32::EPSILON > (a - b); | ||
| ^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a - b).abs()` | ||
|
||
error: float equality check without `.abs()` | ||
--> $DIR/float_equality_without_abs.rs:20:13 | ||
| | ||
LL | let _ = f32::EPSILON > a - b; | ||
| ^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a - b).abs()` | ||
|
||
error: float equality check without `.abs()` | ||
--> $DIR/float_equality_without_abs.rs:21:13 | ||
| | ||
LL | let _ = f32::EPSILON > a - b.abs(); | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a - b.abs()).abs()` | ||
|
||
error: float equality check without `.abs()` | ||
--> $DIR/float_equality_without_abs.rs:22:13 | ||
| | ||
LL | let _ = f64::EPSILON > (a as f64 - b as f64); | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(a as f64 - b as f64).abs()` | ||
|
||
error: float equality check without `.abs()` | ||
--> $DIR/float_equality_without_abs.rs:23:13 | ||
| | ||
LL | let _ = f32::EPSILON > 1.0 - 2.0; | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: add `.abs()`: `(1.0 - 2.0).abs()` | ||
|
||
error: aborting due to 11 previous errors | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be done as
if let ExprKind::Binary(Lt | Rt, left, right) ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if I get this wrong, but I still need to differentiate between
BinOpKind::Lt
andBinOpKind::Gt
. If I would use something likeif let ExprKind::Binary( Spanned { node: BinOpKind::Lt | BinOpKind::Gt, .. }, left, right, ) = ...
Then I would still need to sort out whether my Operator is
<
or>
. Or am I wrong?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh sorry, you're absolutely right