Skip to content

Commit 77ae3ed

Browse files
committed
Lint (x * x + y * y).sqrt() => x.hypot(y)
1 parent 52b75d3 commit 77ae3ed

10 files changed

+177
-6
lines changed

clippy_lints/src/floating_point_arithmetic.rs

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use crate::consts::{
22
constant, constant_simple, Constant,
33
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};
@@ -296,6 +296,17 @@ fn check_powf(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
296296
fn check_powi(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
297297
// Check argument
298298
if let Some((value, _)) = constant(cx, cx.tables, &args[1]) {
299+
// TODO: need more specific check. this is too wide. remember also to include tests
300+
if let Some(parent) = get_parent_expr(cx, expr) {
301+
if let Some(grandparent) = get_parent_expr(cx, parent) {
302+
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args) = grandparent.kind {
303+
if method_name.as_str() == "sqrt" && detect_hypot(cx, args).is_some() {
304+
return;
305+
}
306+
}
307+
}
308+
}
309+
299310
let (lint, help, suggestion) = match value {
300311
Int(2) => (
301312
IMPRECISE_FLOPS,
@@ -317,6 +328,57 @@ fn check_powi(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
317328
}
318329
}
319330

331+
fn detect_hypot(cx: &LateContext<'_, '_>, args: &[Expr<'_>]) -> Option<String> {
332+
if let ExprKind::Binary(
333+
Spanned {
334+
node: BinOpKind::Add, ..
335+
},
336+
ref add_lhs,
337+
ref add_rhs,
338+
) = args[0].kind
339+
{
340+
// check if expression of the form x * x + y * y
341+
if_chain! {
342+
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref lmul_lhs, ref lmul_rhs) = add_lhs.kind;
343+
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref rmul_lhs, ref rmul_rhs) = add_rhs.kind;
344+
if are_exprs_equal(cx, lmul_lhs, lmul_rhs);
345+
if are_exprs_equal(cx, rmul_lhs, rmul_rhs);
346+
then {
347+
return Some(format!("{}.hypot({})", Sugg::hir(cx, &lmul_lhs, ".."), Sugg::hir(cx, &rmul_lhs, "..")));
348+
}
349+
}
350+
351+
// check if expression of the form x.powi(2) + y.powi(2)
352+
if_chain! {
353+
if let ExprKind::MethodCall(PathSegment { ident: lmethod_name, .. }, ref _lspan, ref largs) = add_lhs.kind;
354+
if let ExprKind::MethodCall(PathSegment { ident: rmethod_name, .. }, ref _rspan, ref rargs) = add_rhs.kind;
355+
if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi";
356+
if let Some((lvalue, _)) = constant(cx, cx.tables, &largs[1]);
357+
if let Some((rvalue, _)) = constant(cx, cx.tables, &rargs[1]);
358+
if Int(2) == lvalue && Int(2) == rvalue;
359+
then {
360+
return Some(format!("{}.hypot({})", Sugg::hir(cx, &largs[0], ".."), Sugg::hir(cx, &rargs[0], "..")));
361+
}
362+
}
363+
}
364+
365+
None
366+
}
367+
368+
fn check_hypot(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
369+
if let Some(message) = detect_hypot(cx, args) {
370+
span_lint_and_sugg(
371+
cx,
372+
IMPRECISE_FLOPS,
373+
expr.span,
374+
"hypotenuse can be computed more accurately",
375+
"consider using",
376+
message,
377+
Applicability::MachineApplicable,
378+
);
379+
}
380+
}
381+
320382
// TODO: Lint expressions of the form `x.exp() - y` where y > 1
321383
// and suggest usage of `x.exp_m1() - (y - 1)` instead
322384
fn check_expm1(cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
@@ -368,6 +430,14 @@ fn check_mul_add(cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
368430
rhs,
369431
) = &expr.kind
370432
{
433+
if let Some(parent) = get_parent_expr(cx, expr) {
434+
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args) = parent.kind {
435+
if method_name.as_str() == "sqrt" && detect_hypot(cx, args).is_some() {
436+
return;
437+
}
438+
}
439+
}
440+
371441
let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs) {
372442
(inner_lhs, inner_rhs, rhs)
373443
} else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs) {
@@ -514,6 +584,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FloatingPointArithmetic {
514584
"log" => check_log_base(cx, expr, args),
515585
"powf" => check_powf(cx, expr, args),
516586
"powi" => check_powi(cx, expr, args),
587+
"sqrt" => check_hypot(cx, expr, args),
517588
_ => {},
518589
}
519590
}

tests/ui/floating_point_hypot.fixed

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// run-rustfix
2+
#![warn(clippy::suboptimal_flops, 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+
let _ = x.mul_add(x, y * y).sqrt();
12+
let _ = x.mul_add(4f32, y * y).sqrt();
13+
}

tests/ui/floating_point_hypot.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// run-rustfix
2+
#![warn(clippy::suboptimal_flops, 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+
let _ = x.mul_add(x, y * y).sqrt();
12+
let _ = (x * 4f32 + y * y).sqrt();
13+
}

tests/ui/floating_point_hypot.stderr

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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: multiply and add expressions can be calculated more efficiently and accurately
22+
--> $DIR/floating_point_hypot.rs:12:13
23+
|
24+
LL | let _ = (x * 4f32 + y * y).sqrt();
25+
| ^^^^^^^^^^^^^^^^^^ help: consider using: `x.mul_add(4f32, y * y)`
26+
|
27+
= note: `-D clippy::suboptimal-flops` implied by `-D warnings`
28+
29+
error: aborting due to 4 previous errors
30+

tests/ui/floating_point_mul_add.fixed

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,9 @@ fn main() {
1818

1919
let _ = a.mul_add(b, c).mul_add(a.mul_add(b, c), a.mul_add(b, c)) + c;
2020
let _ = 1234.567_f64.mul_add(45.67834_f64, 0.0004_f64);
21+
22+
let _ = a.mul_add(a, b).sqrt();
23+
24+
// Cases where the lint shouldn't be applied
25+
let _ = (a * a + b * b).sqrt();
2126
}

tests/ui/floating_point_mul_add.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,9 @@ fn main() {
1818

1919
let _ = a.mul_add(b, c) * a.mul_add(b, c) + a.mul_add(b, c) + c;
2020
let _ = 1234.567_f64 * 45.67834_f64 + 0.0004_f64;
21+
22+
let _ = (a * a + b).sqrt();
23+
24+
// Cases where the lint shouldn't be applied
25+
let _ = (a * a + b * b).sqrt();
2126
}

tests/ui/floating_point_mul_add.stderr

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,11 @@ error: multiply and add expressions can be calculated more efficiently and accur
5454
LL | let _ = 1234.567_f64 * 45.67834_f64 + 0.0004_f64;
5555
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1234.567_f64.mul_add(45.67834_f64, 0.0004_f64)`
5656

57-
error: aborting due to 9 previous errors
57+
error: multiply and add expressions can be calculated more efficiently and accurately
58+
--> $DIR/floating_point_mul_add.rs:22:13
59+
|
60+
LL | let _ = (a * a + b).sqrt();
61+
| ^^^^^^^^^^^ help: consider using: `a.mul_add(a, b)`
62+
63+
error: aborting due to 10 previous errors
5864

tests/ui/floating_point_powi.fixed

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
// run-rustfix
2-
#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)]
2+
#![warn(clippy::imprecise_flops)]
33

44
fn main() {
55
let one = 1;
66
let x = 3f32;
77
let _ = x * x;
88
let _ = x * x;
9+
10+
let y = 4f32;
11+
let _ = (x * x + y).sqrt();
12+
let _ = (x + y * y).sqrt();
913
// Cases where the lint shouldn't be applied
1014
let _ = x.powi(3);
1115
let _ = x.powi(one + 1);
16+
let _ = x.hypot(y);
1217
}

tests/ui/floating_point_powi.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
// run-rustfix
2-
#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)]
2+
#![warn(clippy::imprecise_flops)]
33

44
fn main() {
55
let one = 1;
66
let x = 3f32;
77
let _ = x.powi(2);
88
let _ = x.powi(1 + 1);
9+
10+
let y = 4f32;
11+
let _ = (x.powi(2) + y).sqrt();
12+
let _ = (x + y.powi(2)).sqrt();
913
// Cases where the lint shouldn't be applied
1014
let _ = x.powi(3);
1115
let _ = x.powi(one + 1);
16+
let _ = (x.powi(2) + y.powi(2)).sqrt();
1217
}

tests/ui/floating_point_powi.stderr

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,23 @@ error: square can be computed more accurately
1212
LL | let _ = x.powi(1 + 1);
1313
| ^^^^^^^^^^^^^ help: consider using: `x * x`
1414

15-
error: aborting due to 2 previous errors
15+
error: square can be computed more accurately
16+
--> $DIR/floating_point_powi.rs:11:14
17+
|
18+
LL | let _ = (x.powi(2) + y).sqrt();
19+
| ^^^^^^^^^ help: consider using: `x * x`
20+
21+
error: square can be computed more accurately
22+
--> $DIR/floating_point_powi.rs:12:18
23+
|
24+
LL | let _ = (x + y.powi(2)).sqrt();
25+
| ^^^^^^^^^ help: consider using: `y * y`
26+
27+
error: hypotenuse can be computed more accurately
28+
--> $DIR/floating_point_powi.rs:16:13
29+
|
30+
LL | let _ = (x.powi(2) + y.powi(2)).sqrt();
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.hypot(y)`
32+
33+
error: aborting due to 5 previous errors
1634

0 commit comments

Comments
 (0)