Skip to content

Commit 75d43aa

Browse files
authored
Rollup merge of rust-lang#5443 - thiagoarrais:issue-2040, r=flip1995
Some accuracy lints for floating point operations This will add some lints for accuracy on floating point operations suggested by @clarfon in rust-lang#2040 (fixes rust-lang#2040). These are the remaining lints: - [x] x.powi(2) => x * x - [x] x.logN() / y.logN() => x.logbase(y) - [x] x.logbase(E) => x.log() - [x] x.logbase(10) => x.log10() - [x] x.logbase(2) => x.log2(). - [x] x * PI / 180 => x.to_radians() - [x] x * 180 / PI => x.to_degrees() - [x] (x + 1).log() => x.log_1p() - [x] sqrt(x * x + y * y) => x.hypot(y) changelog: Included some accuracy lints for floating point operations
2 parents 7d611d9 + 3065201 commit 75d43aa

22 files changed

+499
-32
lines changed

clippy_lints/src/floating_point_arithmetic.rs

+224-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use crate::consts::{
22
constant, constant_simple, Constant,
3-
Constant::{F32, F64},
3+
Constant::{Int, F32, F64},
44
};
5-
use crate::utils::{higher, numeric_literal, span_lint_and_sugg, sugg, SpanlessEq};
5+
use crate::utils::{get_parent_expr, higher, numeric_literal, span_lint_and_sugg, sugg, SpanlessEq};
66
use if_chain::if_chain;
77
use rustc_errors::Applicability;
8-
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
8+
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp};
99
use rustc_lint::{LateContext, LateLintPass};
1010
use rustc_middle::ty;
1111
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -293,6 +293,121 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
293293
}
294294
}
295295

296+
fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
297+
if let Some((value, _)) = constant(cx, cx.tables(), &args[1]) {
298+
if value == Int(2) {
299+
if let Some(parent) = get_parent_expr(cx, expr) {
300+
if let Some(grandparent) = get_parent_expr(cx, parent) {
301+
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args, _) = grandparent.kind {
302+
if method_name.as_str() == "sqrt" && detect_hypot(cx, args).is_some() {
303+
return;
304+
}
305+
}
306+
}
307+
308+
if let ExprKind::Binary(
309+
Spanned {
310+
node: BinOpKind::Add, ..
311+
},
312+
ref lhs,
313+
ref rhs,
314+
) = parent.kind
315+
{
316+
let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs };
317+
318+
span_lint_and_sugg(
319+
cx,
320+
SUBOPTIMAL_FLOPS,
321+
parent.span,
322+
"square can be computed more efficiently",
323+
"consider using",
324+
format!(
325+
"{}.mul_add({}, {})",
326+
Sugg::hir(cx, &args[0], ".."),
327+
Sugg::hir(cx, &args[0], ".."),
328+
Sugg::hir(cx, &other_addend, ".."),
329+
),
330+
Applicability::MachineApplicable,
331+
);
332+
333+
return;
334+
}
335+
}
336+
337+
span_lint_and_sugg(
338+
cx,
339+
SUBOPTIMAL_FLOPS,
340+
expr.span,
341+
"square can be computed more efficiently",
342+
"consider using",
343+
format!("{} * {}", Sugg::hir(cx, &args[0], ".."), Sugg::hir(cx, &args[0], "..")),
344+
Applicability::MachineApplicable,
345+
);
346+
}
347+
}
348+
}
349+
350+
fn detect_hypot(cx: &LateContext<'_>, args: &[Expr<'_>]) -> Option<String> {
351+
if let ExprKind::Binary(
352+
Spanned {
353+
node: BinOpKind::Add, ..
354+
},
355+
ref add_lhs,
356+
ref add_rhs,
357+
) = args[0].kind
358+
{
359+
// check if expression of the form x * x + y * y
360+
if_chain! {
361+
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref lmul_lhs, ref lmul_rhs) = add_lhs.kind;
362+
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref rmul_lhs, ref rmul_rhs) = add_rhs.kind;
363+
if are_exprs_equal(cx, lmul_lhs, lmul_rhs);
364+
if are_exprs_equal(cx, rmul_lhs, rmul_rhs);
365+
then {
366+
return Some(format!("{}.hypot({})", Sugg::hir(cx, &lmul_lhs, ".."), Sugg::hir(cx, &rmul_lhs, "..")));
367+
}
368+
}
369+
370+
// check if expression of the form x.powi(2) + y.powi(2)
371+
if_chain! {
372+
if let ExprKind::MethodCall(
373+
PathSegment { ident: lmethod_name, .. },
374+
ref _lspan,
375+
ref largs,
376+
_
377+
) = add_lhs.kind;
378+
if let ExprKind::MethodCall(
379+
PathSegment { ident: rmethod_name, .. },
380+
ref _rspan,
381+
ref rargs,
382+
_
383+
) = add_rhs.kind;
384+
if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi";
385+
if let Some((lvalue, _)) = constant(cx, cx.tables(), &largs[1]);
386+
if let Some((rvalue, _)) = constant(cx, cx.tables(), &rargs[1]);
387+
if Int(2) == lvalue && Int(2) == rvalue;
388+
then {
389+
return Some(format!("{}.hypot({})", Sugg::hir(cx, &largs[0], ".."), Sugg::hir(cx, &rargs[0], "..")));
390+
}
391+
}
392+
}
393+
394+
None
395+
}
396+
397+
fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
398+
if let Some(message) = detect_hypot(cx, args) {
399+
span_lint_and_sugg(
400+
cx,
401+
IMPRECISE_FLOPS,
402+
expr.span,
403+
"hypotenuse can be computed more accurately",
404+
"consider using",
405+
message,
406+
Applicability::MachineApplicable,
407+
);
408+
}
409+
}
410+
296411
// TODO: Lint expressions of the form `x.exp() - y` where y > 1
297412
// and suggest usage of `x.exp_m1() - (y - 1)` instead
298413
fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
@@ -344,6 +459,14 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
344459
rhs,
345460
) = &expr.kind
346461
{
462+
if let Some(parent) = get_parent_expr(cx, expr) {
463+
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args, _) = parent.kind {
464+
if method_name.as_str() == "sqrt" && detect_hypot(cx, args).is_some() {
465+
return;
466+
}
467+
}
468+
}
469+
347470
let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs) {
348471
(inner_lhs, inner_rhs, rhs)
349472
} else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs) {
@@ -479,6 +602,100 @@ fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
479602
}
480603
}
481604

605+
fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
606+
if_chain! {
607+
if let ExprKind::MethodCall(PathSegment { ident: method_name_a, .. }, _, ref args_a, _) = expr_a.kind;
608+
if let ExprKind::MethodCall(PathSegment { ident: method_name_b, .. }, _, ref args_b, _) = expr_b.kind;
609+
then {
610+
return method_name_a.as_str() == method_name_b.as_str() &&
611+
args_a.len() == args_b.len() &&
612+
(
613+
["ln", "log2", "log10"].contains(&&*method_name_a.as_str()) ||
614+
method_name_a.as_str() == "log" && args_a.len() == 2 && are_exprs_equal(cx, &args_a[1], &args_b[1])
615+
);
616+
}
617+
}
618+
619+
false
620+
}
621+
622+
fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
623+
// check if expression of the form x.logN() / y.logN()
624+
if_chain! {
625+
if let ExprKind::Binary(
626+
Spanned {
627+
node: BinOpKind::Div, ..
628+
},
629+
lhs,
630+
rhs,
631+
) = &expr.kind;
632+
if are_same_base_logs(cx, lhs, rhs);
633+
if let ExprKind::MethodCall(_, _, ref largs, _) = lhs.kind;
634+
if let ExprKind::MethodCall(_, _, ref rargs, _) = rhs.kind;
635+
then {
636+
span_lint_and_sugg(
637+
cx,
638+
SUBOPTIMAL_FLOPS,
639+
expr.span,
640+
"log base can be expressed more clearly",
641+
"consider using",
642+
format!("{}.log({})", Sugg::hir(cx, &largs[0], ".."), Sugg::hir(cx, &rargs[0], ".."),),
643+
Applicability::MachineApplicable,
644+
);
645+
}
646+
}
647+
}
648+
649+
fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
650+
if_chain! {
651+
if let ExprKind::Binary(
652+
Spanned {
653+
node: BinOpKind::Div, ..
654+
},
655+
div_lhs,
656+
div_rhs,
657+
) = &expr.kind;
658+
if let ExprKind::Binary(
659+
Spanned {
660+
node: BinOpKind::Mul, ..
661+
},
662+
mul_lhs,
663+
mul_rhs,
664+
) = &div_lhs.kind;
665+
if let Some((rvalue, _)) = constant(cx, cx.tables(), div_rhs);
666+
if let Some((lvalue, _)) = constant(cx, cx.tables(), mul_rhs);
667+
then {
668+
// TODO: also check for constant values near PI/180 or 180/PI
669+
if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) &&
670+
(F32(180_f32) == lvalue || F64(180_f64) == lvalue)
671+
{
672+
span_lint_and_sugg(
673+
cx,
674+
SUBOPTIMAL_FLOPS,
675+
expr.span,
676+
"conversion to degrees can be done more accurately",
677+
"consider using",
678+
format!("{}.to_degrees()", Sugg::hir(cx, &mul_lhs, "..")),
679+
Applicability::MachineApplicable,
680+
);
681+
} else if
682+
(F32(180_f32) == rvalue || F64(180_f64) == rvalue) &&
683+
(F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue)
684+
{
685+
span_lint_and_sugg(
686+
cx,
687+
SUBOPTIMAL_FLOPS,
688+
expr.span,
689+
"conversion to radians can be done more accurately",
690+
"consider using",
691+
format!("{}.to_radians()", Sugg::hir(cx, &mul_lhs, "..")),
692+
Applicability::MachineApplicable,
693+
);
694+
}
695+
}
696+
}
697+
}
698+
482699
impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
483700
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
484701
if let ExprKind::MethodCall(ref path, _, args, _) = &expr.kind {
@@ -489,13 +706,17 @@ impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
489706
"ln" => check_ln1p(cx, expr, args),
490707
"log" => check_log_base(cx, expr, args),
491708
"powf" => check_powf(cx, expr, args),
709+
"powi" => check_powi(cx, expr, args),
710+
"sqrt" => check_hypot(cx, expr, args),
492711
_ => {},
493712
}
494713
}
495714
} else {
496715
check_expm1(cx, expr);
497716
check_mul_add(cx, expr);
498717
check_custom_abs(cx, expr);
718+
check_log_division(cx, expr);
719+
check_radians(cx, expr);
499720
}
500721
}
501722
}

tests/ui/floating_point_hypot.fixed

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// run-rustfix
2+
#![warn(clippy::imprecise_flops)]
3+
4+
fn main() {
5+
let x = 3f32;
6+
let y = 4f32;
7+
let _ = x.hypot(y);
8+
let _ = (x + 1f32).hypot(y);
9+
let _ = x.hypot(y);
10+
// Cases where the lint shouldn't be applied
11+
// TODO: linting this adds some complexity, but could be done
12+
let _ = x.mul_add(x, y * y).sqrt();
13+
let _ = (x * 4f32 + y * y).sqrt();
14+
}

tests/ui/floating_point_hypot.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// run-rustfix
2+
#![warn(clippy::imprecise_flops)]
3+
4+
fn main() {
5+
let x = 3f32;
6+
let y = 4f32;
7+
let _ = (x * x + y * y).sqrt();
8+
let _ = ((x + 1f32) * (x + 1f32) + y * y).sqrt();
9+
let _ = (x.powi(2) + y.powi(2)).sqrt();
10+
// Cases where the lint shouldn't be applied
11+
// TODO: linting this adds some complexity, but could be done
12+
let _ = x.mul_add(x, y * y).sqrt();
13+
let _ = (x * 4f32 + y * y).sqrt();
14+
}

tests/ui/floating_point_hypot.stderr

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: hypotenuse can be computed more accurately
2+
--> $DIR/floating_point_hypot.rs:7:13
3+
|
4+
LL | let _ = (x * x + y * y).sqrt();
5+
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.hypot(y)`
6+
|
7+
= note: `-D clippy::imprecise-flops` implied by `-D warnings`
8+
9+
error: hypotenuse can be computed more accurately
10+
--> $DIR/floating_point_hypot.rs:8:13
11+
|
12+
LL | let _ = ((x + 1f32) * (x + 1f32) + y * y).sqrt();
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x + 1f32).hypot(y)`
14+
15+
error: hypotenuse can be computed more accurately
16+
--> $DIR/floating_point_hypot.rs:9:13
17+
|
18+
LL | let _ = (x.powi(2) + y.powi(2)).sqrt();
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.hypot(y)`
20+
21+
error: aborting due to 3 previous errors
22+

tests/ui/floating_point_log.fixed

+5-5
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ fn check_ln1p() {
2525
let _ = 2.0f32.ln_1p();
2626
let _ = x.ln_1p();
2727
let _ = (x / 2.0).ln_1p();
28-
let _ = x.powi(2).ln_1p();
29-
let _ = (x.powi(2) / 2.0).ln_1p();
28+
let _ = x.powi(3).ln_1p();
29+
let _ = (x.powi(3) / 2.0).ln_1p();
3030
let _ = ((std::f32::consts::E - 1.0)).ln_1p();
3131
let _ = x.ln_1p();
32-
let _ = x.powi(2).ln_1p();
32+
let _ = x.powi(3).ln_1p();
3333
let _ = (x + 2.0).ln_1p();
3434
let _ = (x / 2.0).ln_1p();
3535
// Cases where the lint shouldn't be applied
@@ -43,9 +43,9 @@ fn check_ln1p() {
4343
let _ = 2.0f64.ln_1p();
4444
let _ = x.ln_1p();
4545
let _ = (x / 2.0).ln_1p();
46-
let _ = x.powi(2).ln_1p();
46+
let _ = x.powi(3).ln_1p();
4747
let _ = x.ln_1p();
48-
let _ = x.powi(2).ln_1p();
48+
let _ = x.powi(3).ln_1p();
4949
let _ = (x + 2.0).ln_1p();
5050
let _ = (x / 2.0).ln_1p();
5151
// Cases where the lint shouldn't be applied

tests/ui/floating_point_log.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ fn check_ln1p() {
2525
let _ = (1f32 + 2.0).ln();
2626
let _ = (1.0 + x).ln();
2727
let _ = (1.0 + x / 2.0).ln();
28-
let _ = (1.0 + x.powi(2)).ln();
29-
let _ = (1.0 + x.powi(2) / 2.0).ln();
28+
let _ = (1.0 + x.powi(3)).ln();
29+
let _ = (1.0 + x.powi(3) / 2.0).ln();
3030
let _ = (1.0 + (std::f32::consts::E - 1.0)).ln();
3131
let _ = (x + 1.0).ln();
32-
let _ = (x.powi(2) + 1.0).ln();
32+
let _ = (x.powi(3) + 1.0).ln();
3333
let _ = (x + 2.0 + 1.0).ln();
3434
let _ = (x / 2.0 + 1.0).ln();
3535
// Cases where the lint shouldn't be applied
@@ -43,9 +43,9 @@ fn check_ln1p() {
4343
let _ = (1f64 + 2.0).ln();
4444
let _ = (1.0 + x).ln();
4545
let _ = (1.0 + x / 2.0).ln();
46-
let _ = (1.0 + x.powi(2)).ln();
46+
let _ = (1.0 + x.powi(3)).ln();
4747
let _ = (x + 1.0).ln();
48-
let _ = (x.powi(2) + 1.0).ln();
48+
let _ = (x.powi(3) + 1.0).ln();
4949
let _ = (x + 2.0 + 1.0).ln();
5050
let _ = (x / 2.0 + 1.0).ln();
5151
// Cases where the lint shouldn't be applied

0 commit comments

Comments
 (0)