|
| 1 | +use clippy_config::Conf; |
| 2 | +use clippy_utils::diagnostics::span_lint_and_then; |
| 3 | +use clippy_utils::msrvs::{self, Msrv}; |
| 4 | +use clippy_utils::source::snippet_with_context; |
| 5 | +use clippy_utils::{is_from_proc_macro, sym}; |
| 6 | +use rustc_errors::Applicability; |
| 7 | +use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; |
| 8 | +use rustc_lint::{LateContext, LateLintPass, LintContext}; |
| 9 | +use rustc_middle::ty::{self, Ty}; |
| 10 | +use rustc_session::impl_lint_pass; |
| 11 | + |
| 12 | +declare_clippy_lint! { |
| 13 | + /// ### What it does |
| 14 | + /// Checks for usage of `T::BITS - x.leading_zeros()` when `x.bit_width()` is available. |
| 15 | + /// |
| 16 | + /// ### Why is this bad? |
| 17 | + /// Manual reimplementations of `bit_width` increase code complexity for little benefit. |
| 18 | + /// |
| 19 | + /// ### Example |
| 20 | + /// ```no_run |
| 21 | + /// let x: u32 = 5; |
| 22 | + /// let bit_width = u32::BITS - x.leading_zeros(); |
| 23 | + /// ``` |
| 24 | + /// Use instead: |
| 25 | + /// ```no_run |
| 26 | + /// let x: u32 = 5; |
| 27 | + /// let bit_width = x.bit_width(); |
| 28 | + /// ``` |
| 29 | + #[clippy::version = "1.98.0"] |
| 30 | + pub MANUAL_BIT_WIDTH, |
| 31 | + pedantic, |
| 32 | + "manually reimplementing `bit_width`" |
| 33 | +} |
| 34 | + |
| 35 | +declare_clippy_lint! { |
| 36 | + /// ### What it does |
| 37 | + /// Checks for usage of `T::BITS - x.leading_zeros()` where T and x are of different types. |
| 38 | + /// |
| 39 | + /// ### Why is this bad? |
| 40 | + /// Substracting `leading_zeros` from the number of bits of another type might be |
| 41 | + /// a buggy implementation of the `bit_width` method. |
| 42 | + /// |
| 43 | + /// ### Example |
| 44 | + /// ```no_run |
| 45 | + /// let x: u64 = 5; |
| 46 | + /// let bit_width = u32::BITS - x.leading_zeros(); |
| 47 | + /// ``` |
| 48 | + /// Use instead: |
| 49 | + /// ```no_run |
| 50 | + /// let x: u64 = 5; |
| 51 | + /// let bit_width = x.bit_width(); |
| 52 | + /// ``` |
| 53 | + #[clippy::version = "1.98.0"] |
| 54 | + pub MISMATCHED_BIT_WIDTH_TYPE, |
| 55 | + suspicious, |
| 56 | + "type mismatch in bit width calculation" |
| 57 | +} |
| 58 | + |
| 59 | +impl_lint_pass!(ManualBitWidth => [MANUAL_BIT_WIDTH, MISMATCHED_BIT_WIDTH_TYPE]); |
| 60 | + |
| 61 | +#[derive(Clone, Copy, PartialEq)] |
| 62 | +enum IntKind<'a> { |
| 63 | + Int(ty::IntTy), |
| 64 | + Uint(ty::UintTy), |
| 65 | + // NOTE: in the following two variants, the inner `Ty` stores the entire `NonZero<T>` |
| 66 | + // and not just `T`. This is so that we can print it in the suggestion. |
| 67 | + NonZero(Ty<'a>), |
| 68 | + NonZeroU(Ty<'a>), |
| 69 | +} |
| 70 | + |
| 71 | +impl IntKind<'_> { |
| 72 | + fn inner_ty(self) -> String { |
| 73 | + match self { |
| 74 | + Self::Int(ty) => ty.name_str().to_string(), |
| 75 | + Self::Uint(ty) => ty.name_str().to_string(), |
| 76 | + Self::NonZero(ty) | Self::NonZeroU(ty) => ty.to_string(), |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + fn suggestion(&self) -> &'static str { |
| 81 | + match self { |
| 82 | + Self::Int(_) => ".cast_unsigned().bit_width()", |
| 83 | + Self::Uint(_) => ".bit_width()", |
| 84 | + Self::NonZero(_) => ".cast_unsigned().bit_width().get()", |
| 85 | + Self::NonZeroU(_) => ".bit_width().get()", |
| 86 | + } |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +pub struct ManualBitWidth { |
| 91 | + msrv: Msrv, |
| 92 | +} |
| 93 | + |
| 94 | +impl ManualBitWidth { |
| 95 | + pub fn new(conf: &Conf) -> Self { |
| 96 | + Self { msrv: conf.msrv } |
| 97 | + } |
| 98 | +} |
| 99 | + |
| 100 | +impl LateLintPass<'_> for ManualBitWidth { |
| 101 | + fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { |
| 102 | + if expr.span.in_external_macro(cx.sess().source_map()) { |
| 103 | + return; |
| 104 | + } |
| 105 | + |
| 106 | + match expr.kind { |
| 107 | + // `T::BITS - n.leading_zeros()` |
| 108 | + ExprKind::Binary(op, left, right) |
| 109 | + if op.node == BinOpKind::Sub |
| 110 | + && let ExprKind::MethodCall(leading_zeros, recv, [], _) = right.kind |
| 111 | + && leading_zeros.ident.name == sym::leading_zeros |
| 112 | + && let ExprKind::Path(QPath::TypeRelative(hir_ty, segment)) = left.kind |
| 113 | + && segment.ident.name == sym::BITS |
| 114 | + && let right_ty = cx.typeck_results().expr_ty(recv) |
| 115 | + && let Some(right_int_kind) = get_int_kind(cx, right_ty) |
| 116 | + && let left_ty = cx.typeck_results().node_type(hir_ty.hir_id) |
| 117 | + && let Some(left_int_kind) = get_int_kind(cx, left_ty) |
| 118 | + && self.msrv.meets(cx, msrvs::BIT_WIDTH) |
| 119 | + && left.span.eq_ctxt(right.span) |
| 120 | + && !is_from_proc_macro(cx, expr) => |
| 121 | + { |
| 122 | + if left_int_kind == right_int_kind { |
| 123 | + // manual implementation of bit_width |
| 124 | + emit_manual_bit_width(cx, recv, expr, right_int_kind); |
| 125 | + } else { |
| 126 | + // mismatched calling types |
| 127 | + emit_type_mismatch(cx, recv, expr, right_int_kind); |
| 128 | + } |
| 129 | + }, |
| 130 | + _ => {}, |
| 131 | + } |
| 132 | + } |
| 133 | +} |
| 134 | + |
| 135 | +fn get_int_kind<'a>(cx: &LateContext<'a>, ty: Ty<'a>) -> Option<IntKind<'a>> { |
| 136 | + match ty.kind() { |
| 137 | + // int::BITS or uint::BITS |
| 138 | + ty::Int(int_ty) => Some(IntKind::Int(*int_ty)), |
| 139 | + ty::Uint(uint_ty) => Some(IntKind::Uint(*uint_ty)), |
| 140 | + // NonZero::<int/uint>::BITS |
| 141 | + ty::Adt(adt, args) if cx.tcx.is_diagnostic_item(sym::NonZero, adt.did()) => { |
| 142 | + let arg = args.type_at(0); |
| 143 | + match arg.kind() { |
| 144 | + ty::Int(_) => Some(IntKind::NonZero(ty)), |
| 145 | + ty::Uint(_) => Some(IntKind::NonZeroU(ty)), |
| 146 | + _ => None, |
| 147 | + } |
| 148 | + }, |
| 149 | + _ => None, |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +fn emit_manual_bit_width(cx: &LateContext<'_>, recv: &Expr<'_>, full_expr: &Expr<'_>, ty_kind: IntKind<'_>) { |
| 154 | + span_lint_and_then( |
| 155 | + cx, |
| 156 | + MANUAL_BIT_WIDTH, |
| 157 | + full_expr.span, |
| 158 | + "manual implementation of `bit_width`", |
| 159 | + |diag| { |
| 160 | + let mut app = Applicability::MachineApplicable; |
| 161 | + let (recv_snip, _) = snippet_with_context(cx, recv.span, full_expr.span.ctxt(), "_", &mut app); |
| 162 | + let suggestion = ty_kind.suggestion(); |
| 163 | + |
| 164 | + diag.span_suggestion_verbose(full_expr.span, "try", format!("{recv_snip}{suggestion}"), app); |
| 165 | + }, |
| 166 | + ); |
| 167 | +} |
| 168 | + |
| 169 | +fn emit_type_mismatch(cx: &LateContext<'_>, recv: &Expr<'_>, full_expr: &Expr<'_>, ty_kind: IntKind<'_>) { |
| 170 | + span_lint_and_then( |
| 171 | + cx, |
| 172 | + MISMATCHED_BIT_WIDTH_TYPE, |
| 173 | + full_expr.span, |
| 174 | + "possible buggy implementation of `bit_width`", |
| 175 | + |diag| { |
| 176 | + diag.note("in order to calculate the bit width, `T::BITS` should match the type of the value calling `.leading_zeros()`"); |
| 177 | + |
| 178 | + let mut app = Applicability::MaybeIncorrect; |
| 179 | + let (recv_snip, _) = snippet_with_context(cx, recv.span, full_expr.span.ctxt(), "_", &mut app); |
| 180 | + let suggestion = ty_kind.suggestion(); |
| 181 | + let x_ty = ty_kind.inner_ty(); |
| 182 | + |
| 183 | + diag.span_suggestion_verbose( |
| 184 | + full_expr.span, |
| 185 | + format!("if you meant to use `{x_ty}::BITS`, use"), |
| 186 | + format!("{recv_snip}{suggestion}"), |
| 187 | + app, |
| 188 | + ); |
| 189 | + }, |
| 190 | + ); |
| 191 | +} |
0 commit comments