| 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 as _, impl_lint_pass}; | |
| use rustc_middle::ty::{self, Ty}; | |
| 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.99.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.99.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.into() } | |
| } | |
| } | |
| impl LateLintPass<'_> for ManualBitWidth { | |
| fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { | |
| // `T::BITS - n.leading_zeros()` | |
| if let ExprKind::Binary(op, left, right) = expr.kind | |
| && 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 | |
| && !expr.span.in_external_macro(cx.sess().source_map()) | |
| && 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)), | |
| _ => 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, | |
| ); | |
| }, | |
| ); | |
| } |