Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6979,6 +6979,7 @@ Released 2018-09-13
[`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert
[`manual_assert_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert_eq
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
[`manual_bit_width`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bit_width
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
[`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals
[`manual_checked_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_checked_ops
Expand Down Expand Up @@ -7071,6 +7072,7 @@ Released 2018-09-13
[`min_ident_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#min_ident_chars
[`min_max`]: https://rust-lang.github.io/rust-clippy/master/index.html#min_max
[`misaligned_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#misaligned_transmute
[`mismatched_bit_width_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#mismatched_bit_width_type
[`mismatched_target_os`]: https://rust-lang.github.io/rust-clippy/master/index.html#mismatched_target_os
[`mismatching_type_param_order`]: https://rust-lang.github.io/rust-clippy/master/index.html#mismatching_type_param_order
[`misnamed_getters`]: https://rust-lang.github.io/rust-clippy/master/index.html#misnamed_getters
Expand Down
191 changes: 191 additions & 0 deletions clippy_lints/src/bit_width.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_context;
use clippy_utils::{is_from_proc_macro, sym};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::{self, Ty};
use rustc_session::impl_lint_pass;

declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `T::BITS - x.leading_zeros()` when `x.bit_width()` is available.
///
/// ### Why is this bad?
/// Manual reimplementations of `bit_width` increase code complexity for little benefit.
///
/// ### Example
/// ```no_run
/// let x: u32 = 5;
/// let bit_width = u32::BITS - x.leading_zeros();
/// ```
/// Use instead:
/// ```no_run
/// let x: u32 = 5;
/// let bit_width = x.bit_width();
/// ```
#[clippy::version = "1.98.0"]
pub MANUAL_BIT_WIDTH,
pedantic,
"manually reimplementing `bit_width`"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `T::BITS - x.leading_zeros()` where T and x are of different types.
///
/// ### Why is this bad?
/// Substracting `leading_zeros` from the number of bits of another type might be
/// a buggy implementation of the `bit_width` method.
///
/// ### Example
/// ```no_run
/// let x: u64 = 5;
/// let bit_width = u32::BITS - x.leading_zeros();
/// ```
/// Use instead:
/// ```no_run
/// let x: u64 = 5;
/// let bit_width = x.bit_width();
/// ```
#[clippy::version = "1.98.0"]
pub MISMATCHED_BIT_WIDTH_TYPE,
suspicious,
"type mismatch in bit width calculation"
}

impl_lint_pass!(ManualBitWidth => [MANUAL_BIT_WIDTH, MISMATCHED_BIT_WIDTH_TYPE]);

#[derive(Clone, Copy, PartialEq)]
enum IntKind<'a> {
Int(ty::IntTy),
Uint(ty::UintTy),
// NOTE: in the following two variants, the inner `Ty` stores the entire `NonZero<T>`
// and not just `T`. This is so that we can print it in the suggestion.
NonZero(Ty<'a>),
NonZeroU(Ty<'a>),
}

impl IntKind<'_> {
fn inner_ty(self) -> String {
match self {
Self::Int(ty) => ty.name_str().to_string(),
Self::Uint(ty) => ty.name_str().to_string(),
Self::NonZero(ty) | Self::NonZeroU(ty) => ty.to_string(),
}
}

fn suggestion(&self) -> &'static str {
match self {
Self::Int(_) => ".cast_unsigned().bit_width()",
Self::Uint(_) => ".bit_width()",
Self::NonZero(_) => ".cast_unsigned().bit_width().get()",
Self::NonZeroU(_) => ".bit_width().get()",
}
}
}

pub struct ManualBitWidth {
msrv: Msrv,
}

impl ManualBitWidth {
pub fn new(conf: &Conf) -> Self {
Self { msrv: conf.msrv }
}
}

impl LateLintPass<'_> for ManualBitWidth {
fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if expr.span.in_external_macro(cx.sess().source_map()) {
return;
}

match expr.kind {
// `T::BITS - n.leading_zeros()`
ExprKind::Binary(op, left, right)
if op.node == BinOpKind::Sub
&& let ExprKind::MethodCall(leading_zeros, recv, [], _) = right.kind
&& leading_zeros.ident.name == sym::leading_zeros
&& let ExprKind::Path(QPath::TypeRelative(hir_ty, segment)) = left.kind
&& segment.ident.name == sym::BITS
&& let right_ty = cx.typeck_results().expr_ty(recv)
&& let Some(right_int_kind) = get_int_kind(cx, right_ty)
&& let left_ty = cx.typeck_results().node_type(hir_ty.hir_id)
&& let Some(left_int_kind) = get_int_kind(cx, left_ty)
&& self.msrv.meets(cx, msrvs::BIT_WIDTH)
&& left.span.eq_ctxt(right.span)
&& !is_from_proc_macro(cx, expr) =>
{
if left_int_kind == right_int_kind {
// manual implementation of bit_width
emit_manual_bit_width(cx, recv, expr, right_int_kind);
} else {
// mismatched calling types
emit_type_mismatch(cx, recv, expr, right_int_kind);
}
},
_ => {},
}
}
}

fn get_int_kind<'a>(cx: &LateContext<'a>, ty: Ty<'a>) -> Option<IntKind<'a>> {
match ty.kind() {
// int::BITS or uint::BITS
ty::Int(int_ty) => Some(IntKind::Int(*int_ty)),
ty::Uint(uint_ty) => Some(IntKind::Uint(*uint_ty)),
// NonZero::<int/uint>::BITS
ty::Adt(adt, args) if cx.tcx.is_diagnostic_item(sym::NonZero, adt.did()) => {
let arg = args.type_at(0);
match arg.kind() {
ty::Int(_) => Some(IntKind::NonZero(ty)),
ty::Uint(_) => Some(IntKind::NonZeroU(ty)),
Comment thread
ada4a marked this conversation as resolved.
_ => None,
}
},
_ => None,
}
}

fn emit_manual_bit_width(cx: &LateContext<'_>, recv: &Expr<'_>, full_expr: &Expr<'_>, ty_kind: IntKind<'_>) {
span_lint_and_then(
cx,
MANUAL_BIT_WIDTH,
full_expr.span,
"manual implementation of `bit_width`",
|diag| {
let mut app = Applicability::MachineApplicable;
let (recv_snip, _) = snippet_with_context(cx, recv.span, full_expr.span.ctxt(), "_", &mut app);
let suggestion = ty_kind.suggestion();

diag.span_suggestion_verbose(full_expr.span, "try", format!("{recv_snip}{suggestion}"), app);
},
);
}

fn emit_type_mismatch(cx: &LateContext<'_>, recv: &Expr<'_>, full_expr: &Expr<'_>, ty_kind: IntKind<'_>) {
span_lint_and_then(
cx,
MISMATCHED_BIT_WIDTH_TYPE,
full_expr.span,
"possible buggy implementation of `bit_width`",
|diag| {
diag.note("in order to calculate the bit width, `T::BITS` should match the type of the value calling `.leading_zeros()`");

let mut app = Applicability::MaybeIncorrect;
let (recv_snip, _) = snippet_with_context(cx, recv.span, full_expr.span.ctxt(), "_", &mut app);
let suggestion = ty_kind.suggestion();
let x_ty = ty_kind.inner_ty();

diag.span_suggestion_verbose(
full_expr.span,
format!("if you meant to use `{x_ty}::BITS`, use"),
format!("{recv_snip}{suggestion}"),
app,
);
},
);
}
2 changes: 2 additions & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE_INFO,
crate::await_holding_invalid::AWAIT_HOLDING_LOCK_INFO,
crate::await_holding_invalid::AWAIT_HOLDING_REFCELL_REF_INFO,
crate::bit_width::MANUAL_BIT_WIDTH_INFO,
crate::bit_width::MISMATCHED_BIT_WIDTH_TYPE_INFO,
crate::blocks_in_conditions::BLOCKS_IN_CONDITIONS_INFO,
crate::bool_assert_comparison::BOOL_ASSERT_COMPARISON_INFO,
crate::bool_comparison::BOOL_COMPARISON_INFO,
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ mod assigning_clones;
mod async_yields_async;
mod attrs;
mod await_holding_invalid;
mod bit_width;
mod blocks_in_conditions;
mod bool_assert_comparison;
mod bool_comparison;
Expand Down Expand Up @@ -733,6 +734,7 @@ rustc_lint::late_lint_methods!(
NeedlessLateInit: needless_late_init::NeedlessLateInit<'tcx> = needless_late_init::NeedlessLateInit::new(conf),
ReturnSelfNotMustUse: return_self_not_must_use::ReturnSelfNotMustUse = return_self_not_must_use::ReturnSelfNotMustUse,
NumberedFields: init_numbered_fields::NumberedFields = init_numbered_fields::NumberedFields,
ManualBitWidth: bit_width::ManualBitWidth = bit_width::ManualBitWidth::new(conf),
ManualBits: manual_bits::ManualBits = manual_bits::ManualBits::new(conf),
DefaultUnionRepresentation: default_union_representation::DefaultUnionRepresentation = default_union_representation::DefaultUnionRepresentation,
OnlyUsedInRecursion: only_used_in_recursion::OnlyUsedInRecursion = <only_used_in_recursion::OnlyUsedInRecursion>::default(),
Expand Down
2 changes: 1 addition & 1 deletion clippy_utils/src/msrvs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ macro_rules! msrv_aliases {

// names may refer to stabilized feature flags or library items
msrv_aliases! {
1,97,0 { ISOLATE_LOWEST_ONE }
1,97,0 { ISOLATE_LOWEST_ONE, BIT_WIDTH }
1,93,0 { VEC_DEQUE_POP_BACK_IF, VEC_DEQUE_POP_FRONT_IF }
1,91,0 { DURATION_FROM_MINUTES_HOURS }
1,88,0 { LET_CHAINS, AS_CHUNKS }
Expand Down
1 change: 1 addition & 0 deletions clippy_utils/src/sym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ generate! {
AsyncReadExt,
AsyncWriteExt,
BACKSLASH_SINGLE_QUOTE: r"\'",
BITS,
BTreeEntry,
BTreeSet,
Binary,
Expand Down
59 changes: 59 additions & 0 deletions tests/ui/manual_bit_width.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#![warn(clippy::manual_bit_width)]

use core::num::{self, NonZero, NonZeroI32, NonZeroU32};

fn main() {
// `T::BITS - x.leading_zeros()`
// unsigned
let w: u8 = 5;
let _ = w.bit_width(); //~ manual_bit_width
let w: u16 = 5;
let _ = w.bit_width(); //~ manual_bit_width
let w: u32 = 5;
let _ = w.bit_width(); //~ manual_bit_width
let w: u64 = 5;
let _ = w.bit_width(); //~ manual_bit_width
let w: usize = 5;
let _ = w.bit_width(); //~ manual_bit_width

// signed
let x: i8 = -5;
let _ = x.cast_unsigned().bit_width(); //~ manual_bit_width
let x: i16 = -5;
let _ = x.cast_unsigned().bit_width(); //~ manual_bit_width
let x: i32 = -5;
let _ = x.cast_unsigned().bit_width(); //~ manual_bit_width
let x: i64 = -5;
let _ = x.cast_unsigned().bit_width(); //~ manual_bit_width
let x: isize = -5;
let _ = x.cast_unsigned().bit_width(); //~ manual_bit_width

// `NonZero::<T>::BITS - x.leading_zeros()`
// unsigned
let y = NonZero::<u8>::new(5).unwrap();
let _ = y.bit_width().get(); //~ manual_bit_width
let y = NonZero::<u16>::new(5).unwrap();
let _ = y.bit_width().get(); //~ manual_bit_width
let y = NonZero::<u32>::new(5).unwrap();
let _ = y.bit_width().get(); //~ manual_bit_width
let y = NonZero::<u64>::new(5).unwrap();
let _ = y.bit_width().get(); //~ manual_bit_width
let y = NonZero::<usize>::new(5).unwrap();
let _ = y.bit_width().get(); //~ manual_bit_width

// signed
let z = NonZero::<i8>::new(-5).unwrap();
let _ = z.cast_unsigned().bit_width().get(); //~ manual_bit_width
let z = NonZero::<i16>::new(-5).unwrap();
let _ = z.cast_unsigned().bit_width().get(); //~ manual_bit_width
let z = NonZero::<i32>::new(-5).unwrap();
let _ = z.cast_unsigned().bit_width().get(); //~ manual_bit_width
let z = NonZero::<i64>::new(-5).unwrap();
let _ = z.cast_unsigned().bit_width().get(); //~ manual_bit_width
let z = NonZero::<isize>::new(-5).unwrap();
let _ = z.cast_unsigned().bit_width().get(); //~ manual_bit_width

// negative cases.
// left expression is a literal
let z: u32 = 1_000_000 - x.leading_zeros();
}
59 changes: 59 additions & 0 deletions tests/ui/manual_bit_width.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#![warn(clippy::manual_bit_width)]

use core::num::{self, NonZero, NonZeroI32, NonZeroU32};

fn main() {
Comment thread
alv-around marked this conversation as resolved.
// `T::BITS - x.leading_zeros()`
// unsigned
let w: u8 = 5;
let _ = u8::BITS - w.leading_zeros(); //~ manual_bit_width
let w: u16 = 5;
let _ = u16::BITS - w.leading_zeros(); //~ manual_bit_width
let w: u32 = 5;
let _ = u32::BITS - w.leading_zeros(); //~ manual_bit_width
let w: u64 = 5;
let _ = u64::BITS - w.leading_zeros(); //~ manual_bit_width
let w: usize = 5;
let _ = usize::BITS - w.leading_zeros(); //~ manual_bit_width

// signed
Comment thread
ada4a marked this conversation as resolved.
let x: i8 = -5;
let _ = i8::BITS - x.leading_zeros(); //~ manual_bit_width
let x: i16 = -5;
let _ = i16::BITS - x.leading_zeros(); //~ manual_bit_width
let x: i32 = -5;
let _ = i32::BITS - x.leading_zeros(); //~ manual_bit_width
let x: i64 = -5;
let _ = i64::BITS - x.leading_zeros(); //~ manual_bit_width
let x: isize = -5;
let _ = isize::BITS - x.leading_zeros(); //~ manual_bit_width

// `NonZero::<T>::BITS - x.leading_zeros()`
// unsigned
let y = NonZero::<u8>::new(5).unwrap();
let _ = NonZero::<u8>::BITS - y.leading_zeros(); //~ manual_bit_width
let y = NonZero::<u16>::new(5).unwrap();
let _ = NonZero::<u16>::BITS - y.leading_zeros(); //~ manual_bit_width
let y = NonZero::<u32>::new(5).unwrap();
let _ = NonZeroU32::BITS - y.leading_zeros(); //~ manual_bit_width
let y = NonZero::<u64>::new(5).unwrap();
let _ = NonZero::<u64>::BITS - y.leading_zeros(); //~ manual_bit_width
let y = NonZero::<usize>::new(5).unwrap();
let _ = num::NonZero::<usize>::BITS - y.leading_zeros(); //~ manual_bit_width

// signed
let z = NonZero::<i8>::new(-5).unwrap();
let _ = NonZero::<i8>::BITS - z.leading_zeros(); //~ manual_bit_width
let z = NonZero::<i16>::new(-5).unwrap();
let _ = NonZero::<i16>::BITS - z.leading_zeros(); //~ manual_bit_width
let z = NonZero::<i32>::new(-5).unwrap();
let _ = NonZeroI32::BITS - z.leading_zeros(); //~ manual_bit_width
let z = NonZero::<i64>::new(-5).unwrap();
let _ = NonZero::<i64>::BITS - z.leading_zeros(); //~ manual_bit_width
let z = NonZero::<isize>::new(-5).unwrap();
let _ = num::NonZero::<isize>::BITS - z.leading_zeros(); //~ manual_bit_width

// negative cases.
// left expression is a literal
let z: u32 = 1_000_000 - x.leading_zeros();
}
Loading