Skip to content

Commit 12befdd

Browse files
committed
Lint (x * x + y * y).sqrt() => x.hypot(y)
1 parent 808f9a1 commit 12befdd

10 files changed

+181
-6
lines changed

clippy_lints/src/floating_point_arithmetic.rs

+77-2
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};
@@ -298,6 +298,19 @@ fn check_powf(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
298298
fn check_powi(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
299299
// Check argument
300300
if let Some((value, _)) = constant(cx, cx.tables, &args[1]) {
301+
// TODO: need more specific check. this is too wide. remember also to include tests
302+
if let Some(parent) = get_parent_expr(cx, expr) {
303+
if let Some(grandparent) = get_parent_expr(cx, parent) {
304+
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args) = grandparent.kind {
305+
if method_name.as_str() == "sqrt" {
306+
if detect_hypot(cx, args).is_some() {
307+
return;
308+
}
309+
}
310+
}
311+
}
312+
}
313+
301314
let (lint, help, suggestion) = match value {
302315
Int(2) => (
303316
IMPRECISE_FLOPS,
@@ -319,6 +332,57 @@ fn check_powi(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
319332
}
320333
}
321334

335+
fn detect_hypot(cx: &LateContext<'_, '_>, args: &[Expr<'_>]) -> Option<String> {
336+
if let ExprKind::Binary(
337+
Spanned {
338+
node: BinOpKind::Add, ..
339+
},
340+
ref add_lhs,
341+
ref add_rhs,
342+
) = args[0].kind
343+
{
344+
// check if expression of the form x * x + y * y
345+
if_chain! {
346+
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref lmul_lhs, ref lmul_rhs) = add_lhs.kind;
347+
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref rmul_lhs, ref rmul_rhs) = add_rhs.kind;
348+
if are_exprs_equal(cx, lmul_lhs, lmul_rhs);
349+
if are_exprs_equal(cx, rmul_lhs, rmul_rhs);
350+
then {
351+
return Some(format!("{}.hypot({})", Sugg::hir(cx, &lmul_lhs, ".."), Sugg::hir(cx, &rmul_lhs, "..")));
352+
}
353+
}
354+
355+
// check if expression of the form x.powi(2) + y.powi(2)
356+
if_chain! {
357+
if let ExprKind::MethodCall(PathSegment { ident: lmethod_name, .. }, ref _lspan, ref largs) = add_lhs.kind;
358+
if let ExprKind::MethodCall(PathSegment { ident: rmethod_name, .. }, ref _rspan, ref rargs) = add_rhs.kind;
359+
if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi";
360+
if let Some((lvalue, _)) = constant(cx, cx.tables, &largs[1]);
361+
if let Some((rvalue, _)) = constant(cx, cx.tables, &rargs[1]);
362+
if Int(2) == lvalue && Int(2) == rvalue;
363+
then {
364+
return Some(format!("{}.hypot({})", Sugg::hir(cx, &largs[0], ".."), Sugg::hir(cx, &rargs[0], "..")));
365+
}
366+
}
367+
}
368+
369+
None
370+
}
371+
372+
fn check_hypot(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
373+
if let Some(message) = detect_hypot(cx, args) {
374+
span_lint_and_sugg(
375+
cx,
376+
IMPRECISE_FLOPS,
377+
expr.span,
378+
"hypotenuse can be computed more accurately",
379+
"consider using",
380+
message,
381+
Applicability::MachineApplicable,
382+
);
383+
}
384+
}
385+
322386
// TODO: Lint expressions of the form `x.exp() - y` where y > 1
323387
// and suggest usage of `x.exp_m1() - (y - 1)` instead
324388
fn check_expm1(cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
@@ -370,6 +434,16 @@ fn check_mul_add(cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
370434
rhs,
371435
) = &expr.kind
372436
{
437+
if let Some(parent) = get_parent_expr(cx, expr) {
438+
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args) = parent.kind {
439+
if method_name.as_str() == "sqrt" {
440+
if detect_hypot(cx, args).is_some() {
441+
return;
442+
}
443+
}
444+
}
445+
}
446+
373447
let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs) {
374448
(inner_lhs, inner_rhs, rhs)
375449
} else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs) {
@@ -516,6 +590,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FloatingPointArithmetic {
516590
"log" => check_log_base(cx, expr, args),
517591
"powf" => check_powf(cx, expr, args),
518592
"powi" => check_powi(cx, expr, args),
593+
"sqrt" => check_hypot(cx, expr, args),
519594
_ => {},
520595
}
521596
}

tests/ui/floating_point_hypot.fixed

+13
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

+13
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

+30
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

+5
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

+5
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

+7-1
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

+6-1
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

+6-1
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

+19-1
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)