Skip to content

Commit 0c8afa3

Browse files
committed
Lint x.log(b) / y.log(b) => x.log(y)
1 parent f627984 commit 0c8afa3

File tree

4 files changed

+115
-10
lines changed

4 files changed

+115
-10
lines changed

clippy_lints/src/floating_point_arithmetic.rs

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -293,13 +293,13 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
293293
}
294294
}
295295

296-
fn check_powi(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
296+
fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
297297
// Check argument
298-
if let Some((value, _)) = constant(cx, cx.tables, &args[1]) {
298+
if let Some((value, _)) = constant(cx, cx.tables(), &args[1]) {
299299
// TODO: need more specific check. this is too wide. remember also to include tests
300300
if let Some(parent) = get_parent_expr(cx, expr) {
301301
if let Some(grandparent) = get_parent_expr(cx, parent) {
302-
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args) = grandparent.kind {
302+
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args, _) = grandparent.kind {
303303
if method_name.as_str() == "sqrt" && detect_hypot(cx, args).is_some() {
304304
return;
305305
}
@@ -328,7 +328,7 @@ fn check_powi(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
328328
}
329329
}
330330

331-
fn detect_hypot(cx: &LateContext<'_, '_>, args: &[Expr<'_>]) -> Option<String> {
331+
fn detect_hypot(cx: &LateContext<'_>, args: &[Expr<'_>]) -> Option<String> {
332332
if let ExprKind::Binary(
333333
Spanned {
334334
node: BinOpKind::Add, ..
@@ -350,11 +350,11 @@ fn detect_hypot(cx: &LateContext<'_, '_>, args: &[Expr<'_>]) -> Option<String> {
350350

351351
// check if expression of the form x.powi(2) + y.powi(2)
352352
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;
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;
355355
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]);
356+
if let Some((lvalue, _)) = constant(cx, cx.tables(), &largs[1]);
357+
if let Some((rvalue, _)) = constant(cx, cx.tables(), &rargs[1]);
358358
if Int(2) == lvalue && Int(2) == rvalue;
359359
then {
360360
return Some(format!("{}.hypot({})", Sugg::hir(cx, &largs[0], ".."), Sugg::hir(cx, &rargs[0], "..")));
@@ -365,7 +365,7 @@ fn detect_hypot(cx: &LateContext<'_, '_>, args: &[Expr<'_>]) -> Option<String> {
365365
None
366366
}
367367

368-
fn check_hypot(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
368+
fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
369369
if let Some(message) = detect_hypot(cx, args) {
370370
span_lint_and_sugg(
371371
cx,
@@ -431,7 +431,7 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
431431
) = &expr.kind
432432
{
433433
if let Some(parent) = get_parent_expr(cx, expr) {
434-
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args) = parent.kind {
434+
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args, _) = parent.kind {
435435
if method_name.as_str() == "sqrt" && detect_hypot(cx, args).is_some() {
436436
return;
437437
}
@@ -573,6 +573,50 @@ fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
573573
}
574574
}
575575

576+
fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
577+
if_chain! {
578+
if let ExprKind::MethodCall(PathSegment { ident: method_name_a, .. }, _, ref args_a, _) = expr_a.kind;
579+
if let ExprKind::MethodCall(PathSegment { ident: method_name_b, .. }, _, ref args_b, _) = expr_b.kind;
580+
then {
581+
return method_name_a.as_str() == method_name_b.as_str() &&
582+
args_a.len() == args_b.len() &&
583+
(
584+
["ln", "log2", "log10"].contains(&&*method_name_a.as_str()) ||
585+
method_name_a.as_str() == "log" && args_a.len() == 2 && are_exprs_equal(cx, &args_a[1], &args_b[1])
586+
);
587+
}
588+
}
589+
590+
false
591+
}
592+
593+
fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
594+
// check if expression of the form x.logN() / y.logN()
595+
if_chain! {
596+
if let ExprKind::Binary(
597+
Spanned {
598+
node: BinOpKind::Div, ..
599+
},
600+
lhs,
601+
rhs,
602+
) = &expr.kind;
603+
if are_same_base_logs(cx, lhs, rhs);
604+
if let ExprKind::MethodCall(_, _, ref largs, _) = lhs.kind;
605+
if let ExprKind::MethodCall(_, _, ref rargs, _) = rhs.kind;
606+
then {
607+
span_lint_and_sugg(
608+
cx,
609+
SUBOPTIMAL_FLOPS,
610+
expr.span,
611+
"division of logarithms can be calculated more efficiently and accurately",
612+
"consider using",
613+
format!("{}.log({})", Sugg::hir(cx, &largs[0], ".."), Sugg::hir(cx, &rargs[0], ".."),),
614+
Applicability::MachineApplicable,
615+
);
616+
}
617+
}
618+
}
619+
576620
impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
577621
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
578622
if let ExprKind::MethodCall(ref path, _, args, _) = &expr.kind {
@@ -592,6 +636,7 @@ impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
592636
check_expm1(cx, expr);
593637
check_mul_add(cx, expr);
594638
check_custom_abs(cx, expr);
639+
check_log_division(cx, expr);
595640
}
596641
}
597642
}

tests/ui/floating_point_logbase.fixed

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// run-rustfix
2+
#![warn(clippy::suboptimal_flops)]
3+
4+
fn main() {
5+
let x = 3f32;
6+
let y = 5f32;
7+
let _ = x.log(y);
8+
let _ = x.log(y);
9+
let _ = x.log(y);
10+
let _ = x.log(y);
11+
// Cases where the lint shouldn't be applied
12+
let _ = x.ln() / y.powf(3.2);
13+
let _ = x.powf(3.2) / y.powf(3.2);
14+
let _ = x.powf(3.2) / y.ln();
15+
let _ = x.log(5f32) / y.log(7f32);
16+
}

tests/ui/floating_point_logbase.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// run-rustfix
2+
#![warn(clippy::suboptimal_flops)]
3+
4+
fn main() {
5+
let x = 3f32;
6+
let y = 5f32;
7+
let _ = x.ln() / y.ln();
8+
let _ = x.log2() / y.log2();
9+
let _ = x.log10() / y.log10();
10+
let _ = x.log(5f32) / y.log(5f32);
11+
// Cases where the lint shouldn't be applied
12+
let _ = x.ln() / y.powf(3.2);
13+
let _ = x.powf(3.2) / y.powf(3.2);
14+
let _ = x.powf(3.2) / y.ln();
15+
let _ = x.log(5f32) / y.log(7f32);
16+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error: division of logarithms can be calculated more efficiently and accurately
2+
--> $DIR/floating_point_logbase.rs:7:13
3+
|
4+
LL | let _ = x.ln() / y.ln();
5+
| ^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
6+
|
7+
= note: `-D clippy::suboptimal-flops` implied by `-D warnings`
8+
9+
error: division of logarithms can be calculated more efficiently and accurately
10+
--> $DIR/floating_point_logbase.rs:8:13
11+
|
12+
LL | let _ = x.log2() / y.log2();
13+
| ^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
14+
15+
error: division of logarithms can be calculated more efficiently and accurately
16+
--> $DIR/floating_point_logbase.rs:9:13
17+
|
18+
LL | let _ = x.log10() / y.log10();
19+
| ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
20+
21+
error: division of logarithms can be calculated more efficiently and accurately
22+
--> $DIR/floating_point_logbase.rs:10:13
23+
|
24+
LL | let _ = x.log(5f32) / y.log(5f32);
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
26+
27+
error: aborting due to 4 previous errors
28+

0 commit comments

Comments
 (0)