Skip to content

New lint [inefficient_pow] #11057

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4901,6 +4901,7 @@ Released 2018-09-13
[`index_refutable_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice
[`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing
[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask
[`inefficient_pow`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_pow
[`inefficient_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string
[`infallible_destructuring_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#infallible_destructuring_match
[`infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#infinite_iter
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::GET_LAST_WITH_LEN_INFO,
crate::methods::GET_UNWRAP_INFO,
crate::methods::IMPLICIT_CLONE_INFO,
crate::methods::INEFFICIENT_POW_INFO,
crate::methods::INEFFICIENT_TO_STRING_INFO,
crate::methods::INSPECT_FOR_EACH_INFO,
crate::methods::INTO_ITER_ON_REF_INFO,
Expand Down
51 changes: 51 additions & 0 deletions clippy_lints/src/methods/inefficient_pow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_from_proc_macro;
use clippy_utils::source::snippet_opt;
use rustc_ast::{LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;

use super::INEFFICIENT_POW;

#[expect(
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::cast_sign_loss,
clippy::float_cmp
)]
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, recv: &Expr<'_>, arg: &Expr<'_>) {
if let ExprKind::Lit(kind) = recv.kind
&& let LitKind::Int(val, suffix) = kind.node
&& let power_used = f32::log2(val as f32)
// Precision loss means it's not a power of two
&& power_used == (power_used as u32) as f32
// `0` would be `1.pow()`, which we shouldn't lint (but should be disallowed in a separate lint)
&& power_used != 0.0
&& let power_used = power_used as u32
&& !is_from_proc_macro(cx, expr)
&& let Some(arg_snippet) = snippet_opt(cx, arg.span)
{
let suffix = match suffix {
LitIntType::Signed(int) => int.name_str(),
LitIntType::Unsigned(int) => int.name_str(),
LitIntType::Unsuffixed => "",
};
// `lhs * 1` is useless
let rhs = if power_used == 1 {
arg_snippet.to_string()
} else {
format!("({arg_snippet} * {power_used})")
};

span_lint_and_sugg(
cx,
INEFFICIENT_POW,
expr.span,
"inefficient `.pow()`",
"use `<<` instead",
format!("1{suffix} << {rhs}"),
Applicability::MaybeIncorrect,
);
}
}
36 changes: 36 additions & 0 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ mod get_first;
mod get_last_with_len;
mod get_unwrap;
mod implicit_clone;
mod inefficient_pow;
mod inefficient_to_string;
mod inspect_for_each;
mod into_iter_on_ref;
Expand Down Expand Up @@ -3444,6 +3445,37 @@ declare_clippy_lint! {
"`format!`ing every element in a collection, then collecting the strings into a new `String`"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for calls to `.pow()` that get the power of two of `n`, power of four, etc.
///
/// ### Why is this bad?
/// It's not, but it's not optimized down to a simple `<<`, thus, it's slower. This lint is
/// available for when that additional performance is absolutely necessary, at the cost of
/// readability.
///
/// ### Known issues
/// If the linted `pow` would overflow, the suggested `<<` will give an incorrect value with
/// overflow checks off. In these cases, `pow` will return 0 whilst `<<` will continue on as
/// usual. It may also fail to compile if `arithmetic_overflow` is denied. If this happens,
/// it likely means the original code was incorrect regardless as it would always return `0`.
///
/// ### Example
/// ```rust,ignore
/// let _ = 2u32.pow(n);
/// let _ = 32u32.pow(n);
/// ```
/// Use instead:
/// ```rust,ignore
/// let _ = 1u32 << n;
/// let _ = 1u32 << (n * 5);
/// ```
#[clippy::version = "1.72.0"]
pub INEFFICIENT_POW,
restriction,
"usage of `.pow()` when `<<` is faster"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `.skip(0)` on iterators.
Expand Down Expand Up @@ -3602,6 +3634,7 @@ impl_lint_pass!(Methods => [
FORMAT_COLLECT,
STRING_LIT_CHARS_ANY,
ITER_SKIP_ZERO,
INEFFICIENT_POW,
]);

/// Extracts a method call name, args, and `Span` of the method name.
Expand Down Expand Up @@ -4008,6 +4041,9 @@ impl Methods {
unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
}
},
("pow", [arg]) => {
inefficient_pow::check(cx, expr, recv, arg);
}
("push", [arg]) => {
path_buf_push_overwrite::check(cx, expr, arg);
},
Expand Down
54 changes: 54 additions & 0 deletions tests/ui/inefficient_pow.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//@run-rustfix
//@aux-build:proc_macros.rs:proc-macro
#![allow(
arithmetic_overflow,
clippy::erasing_op,
clippy::identity_op,
clippy::no_effect,
unused
)]
#![warn(clippy::inefficient_pow)]

#[macro_use]
extern crate proc_macros;

fn main() {
_ = 1i32 << 1;
_ = 1i32 << 0;
_ = 1i32 << 3;
_ = 1i32 << 100;
_ = 1i32 << (3 * 2);
_ = 1i32 << (3 * 5);
_ = 1i32 << (3 * 6);
let n = 3;
_ = 1i32 << (n * 6);
// Overflows, so wrong return value, but that's fine. `arithmetic_overflow` will deny this anyway
_ = 1i32 << (3 * 12);
_ = 1i32 << (3 * 16);
// Don't lint
_ = 0i32.pow(3);
_ = 1i32.pow(3);
external! {
_ = 2i32.pow(1);
_ = 2i32.pow(0);
_ = 2i32.pow(3);
_ = 2i32.pow(100);
_ = 4i32.pow(3);
_ = 32i32.pow(3);
_ = 64i32.pow(3);
_ = 4096i32.pow(3);
_ = 65536i32.pow(3);
}
with_span! {
span
_ = 2i32.pow(1);
_ = 2i32.pow(0);
_ = 2i32.pow(3);
_ = 2i32.pow(100);
_ = 4i32.pow(3);
_ = 32i32.pow(3);
_ = 64i32.pow(3);
_ = 4096i32.pow(3);
_ = 65536i32.pow(3);
}
}
54 changes: 54 additions & 0 deletions tests/ui/inefficient_pow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//@run-rustfix
//@aux-build:proc_macros.rs:proc-macro
#![allow(
arithmetic_overflow,
clippy::erasing_op,
clippy::identity_op,
clippy::no_effect,
unused
)]
#![warn(clippy::inefficient_pow)]

#[macro_use]
extern crate proc_macros;

fn main() {
_ = 2i32.pow(1);
_ = 2i32.pow(0);
_ = 2i32.pow(3);
_ = 2i32.pow(100);
_ = 4i32.pow(3);
_ = 32i32.pow(3);
_ = 64i32.pow(3);
let n = 3;
_ = 64i32.pow(n);
// Overflows, so wrong return value, but that's fine. `arithmetic_overflow` will deny this anyway
_ = 4096i32.pow(3);
_ = 65536i32.pow(3);
// Don't lint
_ = 0i32.pow(3);
_ = 1i32.pow(3);
external! {
_ = 2i32.pow(1);
_ = 2i32.pow(0);
_ = 2i32.pow(3);
_ = 2i32.pow(100);
_ = 4i32.pow(3);
_ = 32i32.pow(3);
_ = 64i32.pow(3);
_ = 4096i32.pow(3);
_ = 65536i32.pow(3);
}
with_span! {
span
_ = 2i32.pow(1);
_ = 2i32.pow(0);
_ = 2i32.pow(3);
_ = 2i32.pow(100);
_ = 4i32.pow(3);
_ = 32i32.pow(3);
_ = 64i32.pow(3);
_ = 4096i32.pow(3);
_ = 65536i32.pow(3);
}
}
64 changes: 64 additions & 0 deletions tests/ui/inefficient_pow.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
error: inefficient `.pow()`
--> $DIR/inefficient_pow.rs:16:9
|
LL | _ = 2i32.pow(1);
| ^^^^^^^^^^^ help: use `<<` instead: `1i32 << 1`
|
= note: `-D clippy::inefficient-pow` implied by `-D warnings`

error: inefficient `.pow()`
--> $DIR/inefficient_pow.rs:17:9
|
LL | _ = 2i32.pow(0);
| ^^^^^^^^^^^ help: use `<<` instead: `1i32 << 0`

error: inefficient `.pow()`
--> $DIR/inefficient_pow.rs:18:9
|
LL | _ = 2i32.pow(3);
| ^^^^^^^^^^^ help: use `<<` instead: `1i32 << 3`

error: inefficient `.pow()`
--> $DIR/inefficient_pow.rs:19:9
|
LL | _ = 2i32.pow(100);
| ^^^^^^^^^^^^^ help: use `<<` instead: `1i32 << 100`

error: inefficient `.pow()`
--> $DIR/inefficient_pow.rs:20:9
|
LL | _ = 4i32.pow(3);
| ^^^^^^^^^^^ help: use `<<` instead: `1i32 << (3 * 2)`

error: inefficient `.pow()`
--> $DIR/inefficient_pow.rs:21:9
|
LL | _ = 32i32.pow(3);
| ^^^^^^^^^^^^ help: use `<<` instead: `1i32 << (3 * 5)`

error: inefficient `.pow()`
--> $DIR/inefficient_pow.rs:22:9
|
LL | _ = 64i32.pow(3);
| ^^^^^^^^^^^^ help: use `<<` instead: `1i32 << (3 * 6)`

error: inefficient `.pow()`
--> $DIR/inefficient_pow.rs:24:9
|
LL | _ = 64i32.pow(n);
| ^^^^^^^^^^^^ help: use `<<` instead: `1i32 << (n * 6)`

error: inefficient `.pow()`
--> $DIR/inefficient_pow.rs:26:9
|
LL | _ = 4096i32.pow(3);
| ^^^^^^^^^^^^^^ help: use `<<` instead: `1i32 << (3 * 12)`

error: inefficient `.pow()`
--> $DIR/inefficient_pow.rs:27:9
|
LL | _ = 65536i32.pow(3);
| ^^^^^^^^^^^^^^^ help: use `<<` instead: `1i32 << (3 * 16)`

error: aborting due to 10 previous errors