Skip to content

Commit 8dc3fde

Browse files
committed
Auto merge of #5246 - JarredAllen:master, r=flip1995
Detect usage of custom floating-point abs implementation Closes #5224 changelog: Enhance [`suboptimal_flops`] lint to detect manual implementations of the `abs` method
2 parents 74eae9d + c3e96d1 commit 8dc3fde

File tree

4 files changed

+429
-2
lines changed

4 files changed

+429
-2
lines changed

clippy_lints/src/floating_point_arithmetic.rs

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::consts::{
2-
constant, Constant,
2+
constant, constant_simple, Constant,
33
Constant::{F32, F64},
44
};
5-
use crate::utils::{span_lint_and_sugg, sugg};
5+
use crate::utils::{higher, span_lint_and_sugg, sugg, SpanlessEq};
66
use if_chain::if_chain;
77
use rustc::ty;
88
use rustc_errors::Applicability;
@@ -72,6 +72,16 @@ declare_clippy_lint! {
7272
/// let _ = a.log(E);
7373
/// let _ = a.powf(2.0);
7474
/// let _ = a * 2.0 + 4.0;
75+
/// let _ = if a < 0.0 {
76+
/// -a
77+
/// } else {
78+
/// a
79+
/// };
80+
/// let _ = if a < 0.0 {
81+
/// a
82+
/// } else {
83+
/// -a
84+
/// };
7585
/// ```
7686
///
7787
/// is better expressed as
@@ -88,6 +98,8 @@ declare_clippy_lint! {
8898
/// let _ = a.ln();
8999
/// let _ = a.powi(2);
90100
/// let _ = a.mul_add(2.0, 4.0);
101+
/// let _ = a.abs();
102+
/// let _ = -a.abs();
91103
/// ```
92104
pub SUBOPTIMAL_FLOPS,
93105
nursery,
@@ -359,6 +371,116 @@ fn check_mul_add(cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
359371
}
360372
}
361373

374+
/// Returns true iff expr is an expression which tests whether or not
375+
/// test is positive or an expression which tests whether or not test
376+
/// is nonnegative.
377+
/// Used for check-custom-abs function below
378+
fn is_testing_positive(cx: &LateContext<'_, '_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
379+
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
380+
match op {
381+
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right) && are_exprs_equal(cx, left, test),
382+
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && are_exprs_equal(cx, right, test),
383+
_ => false,
384+
}
385+
} else {
386+
false
387+
}
388+
}
389+
390+
/// See [`is_testing_positive`]
391+
fn is_testing_negative(cx: &LateContext<'_, '_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
392+
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
393+
match op {
394+
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left) && are_exprs_equal(cx, right, test),
395+
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && are_exprs_equal(cx, left, test),
396+
_ => false,
397+
}
398+
} else {
399+
false
400+
}
401+
}
402+
403+
fn are_exprs_equal(cx: &LateContext<'_, '_>, expr1: &Expr<'_>, expr2: &Expr<'_>) -> bool {
404+
SpanlessEq::new(cx).ignore_fn().eq_expr(expr1, expr2)
405+
}
406+
407+
/// Returns true iff expr is some zero literal
408+
fn is_zero(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool {
409+
match constant_simple(cx, cx.tables, expr) {
410+
Some(Constant::Int(i)) => i == 0,
411+
Some(Constant::F32(f)) => f == 0.0,
412+
Some(Constant::F64(f)) => f == 0.0,
413+
_ => false,
414+
}
415+
}
416+
417+
/// If the two expressions are negations of each other, then it returns
418+
/// a tuple, in which the first element is true iff expr1 is the
419+
/// positive expressions, and the second element is the positive
420+
/// one of the two expressions
421+
/// If the two expressions are not negations of each other, then it
422+
/// returns None.
423+
fn are_negated<'a>(cx: &LateContext<'_, '_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> {
424+
if let ExprKind::Unary(UnOp::UnNeg, expr1_negated) = &expr1.kind {
425+
if are_exprs_equal(cx, expr1_negated, expr2) {
426+
return Some((false, expr2));
427+
}
428+
}
429+
if let ExprKind::Unary(UnOp::UnNeg, expr2_negated) = &expr2.kind {
430+
if are_exprs_equal(cx, expr1, expr2_negated) {
431+
return Some((true, expr1));
432+
}
433+
}
434+
None
435+
}
436+
437+
fn check_custom_abs(cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
438+
if_chain! {
439+
if let Some((cond, body, Some(else_body))) = higher::if_block(&expr);
440+
if let ExprKind::Block(block, _) = body.kind;
441+
if block.stmts.is_empty();
442+
if let Some(if_body_expr) = block.expr;
443+
if let ExprKind::Block(else_block, _) = else_body.kind;
444+
if else_block.stmts.is_empty();
445+
if let Some(else_body_expr) = else_block.expr;
446+
if let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr);
447+
then {
448+
let positive_abs_sugg = (
449+
"manual implementation of `abs` method",
450+
format!("{}.abs()", Sugg::hir(cx, body, "..")),
451+
);
452+
let negative_abs_sugg = (
453+
"manual implementation of negation of `abs` method",
454+
format!("-{}.abs()", Sugg::hir(cx, body, "..")),
455+
);
456+
let sugg = if is_testing_positive(cx, cond, body) {
457+
if if_expr_positive {
458+
positive_abs_sugg
459+
} else {
460+
negative_abs_sugg
461+
}
462+
} else if is_testing_negative(cx, cond, body) {
463+
if if_expr_positive {
464+
negative_abs_sugg
465+
} else {
466+
positive_abs_sugg
467+
}
468+
} else {
469+
return;
470+
};
471+
span_lint_and_sugg(
472+
cx,
473+
SUBOPTIMAL_FLOPS,
474+
expr.span,
475+
sugg.0,
476+
"try",
477+
sugg.1,
478+
Applicability::MachineApplicable,
479+
);
480+
}
481+
}
482+
}
483+
362484
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FloatingPointArithmetic {
363485
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
364486
if let ExprKind::MethodCall(ref path, _, args) = &expr.kind {
@@ -375,6 +497,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FloatingPointArithmetic {
375497
} else {
376498
check_expm1(cx, expr);
377499
check_mul_add(cx, expr);
500+
check_custom_abs(cx, expr);
378501
}
379502
}
380503
}

tests/ui/floating_point_abs.fixed

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// run-rustfix
2+
#![warn(clippy::suboptimal_flops)]
3+
4+
struct A {
5+
a: f64,
6+
b: f64,
7+
}
8+
9+
fn fake_abs1(num: f64) -> f64 {
10+
num.abs()
11+
}
12+
13+
fn fake_abs2(num: f64) -> f64 {
14+
num.abs()
15+
}
16+
17+
fn fake_abs3(a: A) -> f64 {
18+
a.a.abs()
19+
}
20+
21+
fn fake_abs4(num: f64) -> f64 {
22+
num.abs()
23+
}
24+
25+
fn fake_abs5(a: A) -> f64 {
26+
a.a.abs()
27+
}
28+
29+
fn fake_nabs1(num: f64) -> f64 {
30+
-num.abs()
31+
}
32+
33+
fn fake_nabs2(num: f64) -> f64 {
34+
-num.abs()
35+
}
36+
37+
fn fake_nabs3(a: A) -> A {
38+
A {
39+
a: -a.a.abs(),
40+
b: a.b,
41+
}
42+
}
43+
44+
fn not_fake_abs1(num: f64) -> f64 {
45+
if num > 0.0 {
46+
num
47+
} else {
48+
-num - 1f64
49+
}
50+
}
51+
52+
fn not_fake_abs2(num: f64) -> f64 {
53+
if num > 0.0 {
54+
num + 1.0
55+
} else {
56+
-(num + 1.0)
57+
}
58+
}
59+
60+
fn not_fake_abs3(num1: f64, num2: f64) -> f64 {
61+
if num1 > 0.0 {
62+
num2
63+
} else {
64+
-num2
65+
}
66+
}
67+
68+
fn not_fake_abs4(a: A) -> f64 {
69+
if a.a > 0.0 {
70+
a.b
71+
} else {
72+
-a.b
73+
}
74+
}
75+
76+
fn not_fake_abs5(a: A) -> f64 {
77+
if a.a > 0.0 {
78+
a.a
79+
} else {
80+
-a.b
81+
}
82+
}
83+
84+
fn main() {
85+
fake_abs1(5.0);
86+
fake_abs2(5.0);
87+
fake_abs3(A { a: 5.0, b: 5.0 });
88+
fake_abs4(5.0);
89+
fake_abs5(A { a: 5.0, b: 5.0 });
90+
fake_nabs1(5.0);
91+
fake_nabs2(5.0);
92+
fake_nabs3(A { a: 5.0, b: 5.0 });
93+
not_fake_abs1(5.0);
94+
not_fake_abs2(5.0);
95+
not_fake_abs3(5.0, 5.0);
96+
not_fake_abs4(A { a: 5.0, b: 5.0 });
97+
not_fake_abs5(A { a: 5.0, b: 5.0 });
98+
}

tests/ui/floating_point_abs.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// run-rustfix
2+
#![warn(clippy::suboptimal_flops)]
3+
4+
struct A {
5+
a: f64,
6+
b: f64,
7+
}
8+
9+
fn fake_abs1(num: f64) -> f64 {
10+
if num >= 0.0 {
11+
num
12+
} else {
13+
-num
14+
}
15+
}
16+
17+
fn fake_abs2(num: f64) -> f64 {
18+
if 0.0 < num {
19+
num
20+
} else {
21+
-num
22+
}
23+
}
24+
25+
fn fake_abs3(a: A) -> f64 {
26+
if a.a > 0.0 {
27+
a.a
28+
} else {
29+
-a.a
30+
}
31+
}
32+
33+
fn fake_abs4(num: f64) -> f64 {
34+
if 0.0 >= num {
35+
-num
36+
} else {
37+
num
38+
}
39+
}
40+
41+
fn fake_abs5(a: A) -> f64 {
42+
if a.a < 0.0 {
43+
-a.a
44+
} else {
45+
a.a
46+
}
47+
}
48+
49+
fn fake_nabs1(num: f64) -> f64 {
50+
if num < 0.0 {
51+
num
52+
} else {
53+
-num
54+
}
55+
}
56+
57+
fn fake_nabs2(num: f64) -> f64 {
58+
if 0.0 >= num {
59+
num
60+
} else {
61+
-num
62+
}
63+
}
64+
65+
fn fake_nabs3(a: A) -> A {
66+
A {
67+
a: if a.a >= 0.0 { -a.a } else { a.a },
68+
b: a.b,
69+
}
70+
}
71+
72+
fn not_fake_abs1(num: f64) -> f64 {
73+
if num > 0.0 {
74+
num
75+
} else {
76+
-num - 1f64
77+
}
78+
}
79+
80+
fn not_fake_abs2(num: f64) -> f64 {
81+
if num > 0.0 {
82+
num + 1.0
83+
} else {
84+
-(num + 1.0)
85+
}
86+
}
87+
88+
fn not_fake_abs3(num1: f64, num2: f64) -> f64 {
89+
if num1 > 0.0 {
90+
num2
91+
} else {
92+
-num2
93+
}
94+
}
95+
96+
fn not_fake_abs4(a: A) -> f64 {
97+
if a.a > 0.0 {
98+
a.b
99+
} else {
100+
-a.b
101+
}
102+
}
103+
104+
fn not_fake_abs5(a: A) -> f64 {
105+
if a.a > 0.0 {
106+
a.a
107+
} else {
108+
-a.b
109+
}
110+
}
111+
112+
fn main() {
113+
fake_abs1(5.0);
114+
fake_abs2(5.0);
115+
fake_abs3(A { a: 5.0, b: 5.0 });
116+
fake_abs4(5.0);
117+
fake_abs5(A { a: 5.0, b: 5.0 });
118+
fake_nabs1(5.0);
119+
fake_nabs2(5.0);
120+
fake_nabs3(A { a: 5.0, b: 5.0 });
121+
not_fake_abs1(5.0);
122+
not_fake_abs2(5.0);
123+
not_fake_abs3(5.0, 5.0);
124+
not_fake_abs4(A { a: 5.0, b: 5.0 });
125+
not_fake_abs5(A { a: 5.0, b: 5.0 });
126+
}

0 commit comments

Comments
 (0)