Skip to content

Commit 8ee4836

Browse files
committed
New lint [inefficient_pow]
1 parent c7bf05c commit 8ee4836

File tree

7 files changed

+254
-1
lines changed

7 files changed

+254
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4864,6 +4864,7 @@ Released 2018-09-13
48644864
[`index_refutable_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice
48654865
[`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing
48664866
[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask
4867+
[`inefficient_pow`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_pow
48674868
[`inefficient_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string
48684869
[`infallible_destructuring_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#infallible_destructuring_match
48694870
[`infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#infinite_iter

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
341341
crate::methods::GET_LAST_WITH_LEN_INFO,
342342
crate::methods::GET_UNWRAP_INFO,
343343
crate::methods::IMPLICIT_CLONE_INFO,
344+
crate::methods::INEFFICIENT_POW_INFO,
344345
crate::methods::INEFFICIENT_TO_STRING_INFO,
345346
crate::methods::INSPECT_FOR_EACH_INFO,
346347
crate::methods::INTO_ITER_ON_REF_INFO,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use clippy_utils::{diagnostics::span_lint_and_sugg, is_from_proc_macro, source::snippet_opt};
2+
use rustc_ast::{LitIntType, LitKind};
3+
use rustc_errors::Applicability;
4+
use rustc_hir::{Expr, ExprKind};
5+
use rustc_lint::LateContext;
6+
7+
use super::INEFFICIENT_POW;
8+
9+
#[expect(
10+
clippy::cast_possible_truncation,
11+
clippy::cast_precision_loss,
12+
clippy::cast_sign_loss,
13+
clippy::float_cmp
14+
)]
15+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, recv: &Expr<'_>, arg: &Expr<'_>) {
16+
if let ExprKind::Lit(kind) = recv.kind
17+
&& let LitKind::Int(val, suffix) = kind.node
18+
&& let power_used = f32::log2(val as f32)
19+
// Precision loss means it's not a power of two
20+
&& power_used == (power_used as u32) as f32
21+
// `0` would be `1.pow()`, which we shouldn't lint (but should be disallowed in a separate lint)
22+
&& power_used != 0.0
23+
&& let power_used = power_used as u32
24+
&& !is_from_proc_macro(cx, expr)
25+
&& let Some(arg_snippet) = snippet_opt(cx, arg.span)
26+
{
27+
let suffix = match suffix {
28+
LitIntType::Signed(int) => int.name_str(),
29+
LitIntType::Unsigned(int) => int.name_str(),
30+
LitIntType::Unsuffixed => "",
31+
};
32+
33+
span_lint_and_sugg(
34+
cx,
35+
INEFFICIENT_POW,
36+
expr.span,
37+
"inefficient `.pow()`",
38+
"use `<<` instead",
39+
format!("1{suffix} << ({arg_snippet} * {power_used})"),
40+
Applicability::MaybeIncorrect,
41+
);
42+
}
43+
}

clippy_lints/src/methods/mod.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ mod get_first;
3131
mod get_last_with_len;
3232
mod get_unwrap;
3333
mod implicit_clone;
34+
mod inefficient_pow;
3435
mod inefficient_to_string;
3536
mod inspect_for_each;
3637
mod into_iter_on_ref;
@@ -3286,6 +3287,37 @@ declare_clippy_lint! {
32863287
"calling `.drain(..).collect()` to move all elements into a new collection"
32873288
}
32883289

3290+
declare_clippy_lint! {
3291+
/// ### What it does
3292+
/// Checks for calls to `.pow()` that get the power of two of `n`, power of four, etc.
3293+
///
3294+
/// ### Why is this bad?
3295+
/// It's not, but it's not optimized down to a simple `<<`, thus, it's slower. This lint is
3296+
/// available for when that additional performance is absolutely necessary, at the cost of
3297+
/// readability.
3298+
///
3299+
/// ### Known issues
3300+
/// If the linted `pow` would overflow, the suggested `<<` will give an incorrect value with
3301+
/// overflow checks off. In these cases, `pow` will return 0 whilst `<<` will continue on as
3302+
/// usual. It may also fail to compile if `arithmetic_overflow` is denied. If this happens,
3303+
/// it likely means the original code was incorrect regardless as it would always return `0`.
3304+
///
3305+
/// ### Example
3306+
/// ```rust,ignore
3307+
/// let _ = 2u32.pow(n);
3308+
/// let _ = 32u32.pow(n);
3309+
/// ```
3310+
/// Use instead:
3311+
/// ```rust
3312+
/// let _ = 1u32 << n;
3313+
/// let _ = 1u32 << (n * 5);
3314+
/// ```
3315+
#[clippy::version = "1.72.0"]
3316+
pub INEFFICIENT_POW,
3317+
restriction,
3318+
"usage of `.pow()` when `<<` is faster"
3319+
}
3320+
32893321
pub struct Methods {
32903322
avoid_breaking_exported_api: bool,
32913323
msrv: Msrv,
@@ -3416,7 +3448,8 @@ impl_lint_pass!(Methods => [
34163448
CLEAR_WITH_DRAIN,
34173449
MANUAL_NEXT_BACK,
34183450
UNNECESSARY_LITERAL_UNWRAP,
3419-
DRAIN_COLLECT
3451+
DRAIN_COLLECT,
3452+
INEFFICIENT_POW,
34203453
]);
34213454

34223455
/// Extracts a method call name, args, and `Span` of the method name.
@@ -3803,6 +3836,9 @@ impl Methods {
38033836
unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
38043837
}
38053838
},
3839+
("pow", [arg]) => {
3840+
inefficient_pow::check(cx, expr, recv, arg);
3841+
}
38063842
("push", [arg]) => {
38073843
path_buf_push_overwrite::check(cx, expr, arg);
38083844
},

tests/ui/inefficient_pow.fixed

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//@run-rustfix
2+
//@aux-build:proc_macros.rs:proc-macro
3+
#![allow(
4+
arithmetic_overflow,
5+
clippy::erasing_op,
6+
clippy::identity_op,
7+
clippy::no_effect,
8+
unused
9+
)]
10+
#![warn(clippy::inefficient_pow)]
11+
12+
#[macro_use]
13+
extern crate proc_macros;
14+
15+
fn main() {
16+
_ = 1i32 << (1 * 1);
17+
_ = 1i32 << (0 * 1);
18+
_ = 1i32 << (3 * 1);
19+
_ = 1i32 << (100 * 1);
20+
_ = 1i32 << (3 * 2);
21+
_ = 1i32 << (3 * 5);
22+
_ = 1i32 << (3 * 6);
23+
let n = 3;
24+
_ = 1i32 << (n * 6);
25+
// Overflows, so wrong return value, but that's fine. `arithmetic_overflow` will deny this anyway
26+
_ = 1i32 << (3 * 12);
27+
_ = 1i32 << (3 * 16);
28+
// Don't lint
29+
_ = 0i32.pow(3);
30+
_ = 1i32.pow(3);
31+
external! {
32+
_ = 2i32.pow(1);
33+
_ = 2i32.pow(0);
34+
_ = 2i32.pow(3);
35+
_ = 2i32.pow(100);
36+
_ = 4i32.pow(3);
37+
_ = 32i32.pow(3);
38+
_ = 64i32.pow(3);
39+
_ = 4096i32.pow(3);
40+
_ = 65536i32.pow(3);
41+
}
42+
with_span! {
43+
span
44+
_ = 2i32.pow(1);
45+
_ = 2i32.pow(0);
46+
_ = 2i32.pow(3);
47+
_ = 2i32.pow(100);
48+
_ = 4i32.pow(3);
49+
_ = 32i32.pow(3);
50+
_ = 64i32.pow(3);
51+
_ = 4096i32.pow(3);
52+
_ = 65536i32.pow(3);
53+
}
54+
}

tests/ui/inefficient_pow.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//@run-rustfix
2+
//@aux-build:proc_macros.rs:proc-macro
3+
#![allow(
4+
arithmetic_overflow,
5+
clippy::erasing_op,
6+
clippy::identity_op,
7+
clippy::no_effect,
8+
unused
9+
)]
10+
#![warn(clippy::inefficient_pow)]
11+
12+
#[macro_use]
13+
extern crate proc_macros;
14+
15+
fn main() {
16+
_ = 2i32.pow(1);
17+
_ = 2i32.pow(0);
18+
_ = 2i32.pow(3);
19+
_ = 2i32.pow(100);
20+
_ = 4i32.pow(3);
21+
_ = 32i32.pow(3);
22+
_ = 64i32.pow(3);
23+
let n = 3;
24+
_ = 64i32.pow(n);
25+
// Overflows, so wrong return value, but that's fine. `arithmetic_overflow` will deny this anyway
26+
_ = 4096i32.pow(3);
27+
_ = 65536i32.pow(3);
28+
// Don't lint
29+
_ = 0i32.pow(3);
30+
_ = 1i32.pow(3);
31+
external! {
32+
_ = 2i32.pow(1);
33+
_ = 2i32.pow(0);
34+
_ = 2i32.pow(3);
35+
_ = 2i32.pow(100);
36+
_ = 4i32.pow(3);
37+
_ = 32i32.pow(3);
38+
_ = 64i32.pow(3);
39+
_ = 4096i32.pow(3);
40+
_ = 65536i32.pow(3);
41+
}
42+
with_span! {
43+
span
44+
_ = 2i32.pow(1);
45+
_ = 2i32.pow(0);
46+
_ = 2i32.pow(3);
47+
_ = 2i32.pow(100);
48+
_ = 4i32.pow(3);
49+
_ = 32i32.pow(3);
50+
_ = 64i32.pow(3);
51+
_ = 4096i32.pow(3);
52+
_ = 65536i32.pow(3);
53+
}
54+
}

tests/ui/inefficient_pow.stderr

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
error: inefficient `.pow()`
2+
--> $DIR/inefficient_pow.rs:16:9
3+
|
4+
LL | _ = 2i32.pow(1);
5+
| ^^^^^^^^^^^ help: use `<<` instead: `1i32 << (1 * 1)`
6+
|
7+
= note: `-D clippy::inefficient-pow` implied by `-D warnings`
8+
9+
error: inefficient `.pow()`
10+
--> $DIR/inefficient_pow.rs:17:9
11+
|
12+
LL | _ = 2i32.pow(0);
13+
| ^^^^^^^^^^^ help: use `<<` instead: `1i32 << (0 * 1)`
14+
15+
error: inefficient `.pow()`
16+
--> $DIR/inefficient_pow.rs:18:9
17+
|
18+
LL | _ = 2i32.pow(3);
19+
| ^^^^^^^^^^^ help: use `<<` instead: `1i32 << (3 * 1)`
20+
21+
error: inefficient `.pow()`
22+
--> $DIR/inefficient_pow.rs:19:9
23+
|
24+
LL | _ = 2i32.pow(100);
25+
| ^^^^^^^^^^^^^ help: use `<<` instead: `1i32 << (100 * 1)`
26+
27+
error: inefficient `.pow()`
28+
--> $DIR/inefficient_pow.rs:20:9
29+
|
30+
LL | _ = 4i32.pow(3);
31+
| ^^^^^^^^^^^ help: use `<<` instead: `1i32 << (3 * 2)`
32+
33+
error: inefficient `.pow()`
34+
--> $DIR/inefficient_pow.rs:21:9
35+
|
36+
LL | _ = 32i32.pow(3);
37+
| ^^^^^^^^^^^^ help: use `<<` instead: `1i32 << (3 * 5)`
38+
39+
error: inefficient `.pow()`
40+
--> $DIR/inefficient_pow.rs:22:9
41+
|
42+
LL | _ = 64i32.pow(3);
43+
| ^^^^^^^^^^^^ help: use `<<` instead: `1i32 << (3 * 6)`
44+
45+
error: inefficient `.pow()`
46+
--> $DIR/inefficient_pow.rs:24:9
47+
|
48+
LL | _ = 64i32.pow(n);
49+
| ^^^^^^^^^^^^ help: use `<<` instead: `1i32 << (n * 6)`
50+
51+
error: inefficient `.pow()`
52+
--> $DIR/inefficient_pow.rs:26:9
53+
|
54+
LL | _ = 4096i32.pow(3);
55+
| ^^^^^^^^^^^^^^ help: use `<<` instead: `1i32 << (3 * 12)`
56+
57+
error: inefficient `.pow()`
58+
--> $DIR/inefficient_pow.rs:27:9
59+
|
60+
LL | _ = 65536i32.pow(3);
61+
| ^^^^^^^^^^^^^^^ help: use `<<` instead: `1i32 << (3 * 16)`
62+
63+
error: aborting due to 10 previous errors
64+

0 commit comments

Comments
 (0)