| use clippy_config::Conf; |
| use clippy_utils::diagnostics::span_lint_and_then; |
| use clippy_utils::msrvs::Msrv; |
| use clippy_utils::ty::implements_trait; |
| use clippy_utils::{binop_traits, is_from_proc_macro, span_contains_comment, sym}; |
| use rustc_errors::Applicability; |
| use rustc_hir::def_id::DefId; |
| use rustc_hir::{AssignOpKind, BinOpKind, Expr, ExprKind}; |
| use rustc_lint::{LateContext, LateLintPass}; |
| use rustc_middle::ty::{self, Ty}; |
| use rustc_session::impl_lint_pass; |
| use rustc_span::{Span, Symbol}; |
| |
| declare_clippy_lint! { |
| /// ### What it does |
| /// Checks for `NonZero::get` calls that are immediately followed by a method or operator |
| /// which `NonZero` provides itself, with the same return type. |
| /// |
| /// ### Why is this bad? |
| /// The `get` call adds nothing but noise, as the method could be called on the |
| /// `NonZero` value directly. |
| /// |
| /// ### Example |
| /// ```no_run |
| /// # use std::num::NonZero; |
| /// # let nz = NonZero::new(1u32).unwrap(); |
| /// let _ = nz.get().leading_zeros(); |
| /// ``` |
| /// Use instead: |
| /// ```no_run |
| /// # use std::num::NonZero; |
| /// # let nz = NonZero::new(1u32).unwrap(); |
| /// let _ = nz.leading_zeros(); |
| /// ``` |
| /// |
| /// The lint also handles division and remainder operators: |
| /// ```no_run |
| /// # use std::num::NonZero; |
| /// # let nz = NonZero::new(2u32).unwrap(); |
| /// let _ = 4 / nz.get(); |
| /// ``` |
| /// Use instead: |
| /// ```no_run |
| /// # use std::num::NonZero; |
| /// # let nz = NonZero::new(2u32).unwrap(); |
| /// let _ = 4 / nz; |
| /// ``` |
| #[clippy::version = "1.99.0"] |
| pub NEEDLESS_NONZERO_GET, |
| complexity, |
| "unnecessary `NonZero::get` call" |
| } |
| |
| impl_lint_pass!(NeedlessNonzeroGet => [NEEDLESS_NONZERO_GET]); |
| |
| pub struct NeedlessNonzeroGet { |
| msrv: Msrv, |
| } |
| |
| impl NeedlessNonzeroGet { |
| pub fn new(conf: &'static Conf) -> Self { |
| Self { msrv: conf.msrv.into() } |
| } |
| } |
| |
| impl<'tcx> LateLintPass<'tcx> for NeedlessNonzeroGet { |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { |
| match expr.kind { |
| // `<recv>.get().<method>()` |
| ExprKind::MethodCall(method, get_call, [], _) |
| if let Some((recv, nz_ty, nonzero_did, get_span)) = nonzero_get(cx, get_call) |
| // The outer call must resolve to an inherent method. A trait method of the same |
| // name could resolve differently once the receiver becomes a `NonZero`. |
| && let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) |
| && cx.tcx.trait_of_assoc(method_did).is_none() |
| // Dropping `get` must leave the expression's type unchanged. Methods whose |
| // `NonZero` version returns a `NonZero` deliberately do not match. |
| && let ret_ty = cx.typeck_results().expr_ty(expr) |
| && has_matching_method(cx, nonzero_did, nz_ty, method.ident.name, ret_ty, self.msrv) => |
| { |
| emit_unnecessary_get(cx, expr, get_call, recv, get_span, method.ident.name.as_str()); |
| }, |
| |
| // `<lhs> / <recv>.get()` and `<lhs> % <recv>.get()` |
| ExprKind::Binary(op, lhs, get_call) |
| if matches!(op.node, BinOpKind::Div | BinOpKind::Rem) |
| && let Some((recv, nz_ty, _, get_span)) = nonzero_get(cx, get_call) |
| && let Some((trait_lang_item, _)) = binop_traits(op.node) |
| && let Some(trait_id) = cx.tcx.lang_items().get(trait_lang_item) |
| && has_nonzero_operator(cx, lhs, recv, nz_ty, trait_id, self.msrv) => |
| { |
| emit_unnecessary_get(cx, expr, get_call, recv, get_span, op.node.as_str()); |
| }, |
| |
| // `<lhs> /= <recv>.get()` and `<lhs> %= <recv>.get()` |
| ExprKind::AssignOp(op, lhs, get_call) |
| if matches!(op.node, AssignOpKind::DivAssign | AssignOpKind::RemAssign) |
| && let Some((recv, nz_ty, _, get_span)) = nonzero_get(cx, get_call) |
| && let Some((_, trait_lang_item)) = binop_traits(op.node.into()) |
| && let Some(trait_id) = cx.tcx.lang_items().get(trait_lang_item) |
| && has_nonzero_operator(cx, lhs, recv, nz_ty, trait_id, self.msrv) => |
| { |
| emit_unnecessary_get(cx, expr, get_call, recv, get_span, op.node.as_str()); |
| }, |
| |
| _ => {}, |
| } |
| } |
| } |
| |
| /// Returns the receiver, its `NonZero` type and definition, and the `get` identifier span when |
| /// `expr` is an inherent `NonZero::get()` call. |
| fn nonzero_get<'tcx>( |
| cx: &LateContext<'tcx>, |
| expr: &'tcx Expr<'tcx>, |
| ) -> Option<(&'tcx Expr<'tcx>, Ty<'tcx>, DefId, Span)> { |
| let ExprKind::MethodCall(get, recv, [], get_span) = expr.kind else { |
| return None; |
| }; |
| if get.ident.name != sym::get { |
| return None; |
| } |
| let get_did = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; |
| if cx.tcx.trait_of_assoc(get_did).is_some() { |
| return None; |
| } |
| let nz_ty = cx.typeck_results().expr_ty_adjusted(recv); |
| let ty::Adt(adt, _) = nz_ty.kind() else { |
| return None; |
| }; |
| if !cx.tcx.is_diagnostic_item(sym::NonZero, adt.did()) { |
| return None; |
| } |
| |
| Some((recv, nz_ty, adt.did(), get_span)) |
| } |
| |
| fn emit_unnecessary_get<'tcx>( |
| cx: &LateContext<'tcx>, |
| expr: &Expr<'tcx>, |
| get_call: &Expr<'tcx>, |
| recv: &Expr<'tcx>, |
| get_span: Span, |
| operation: &str, |
| ) { |
| // Removing `.get()` means editing the span between the receiver and the surrounding |
| // expression, which is only meaningful when both are written out in the same context. |
| if expr.span.from_expansion() |
| || !recv.span.eq_ctxt(expr.span) |
| || !get_call.span.eq_ctxt(expr.span) |
| || is_from_proc_macro(cx, expr) |
| { |
| return; |
| } |
| |
| span_lint_and_then( |
| cx, |
| NEEDLESS_NONZERO_GET, |
| get_span, |
| format!("unnecessary `get` before `{operation}`"), |
| |diag| { |
| // Covers `.get()` including the dot and any whitespace before it. |
| let removal_span = get_call.span.with_lo(recv.span.hi()); |
| let applicability = if span_contains_comment(cx, removal_span) { |
| Applicability::MaybeIncorrect |
| } else { |
| Applicability::MachineApplicable |
| }; |
| diag.span_suggestion_verbose(removal_span, "remove this", "", applicability); |
| }, |
| ); |
| } |
| |
| /// Checks whether replacing the right-hand primitive operand with its `NonZero` receiver resolves |
| /// to an unsigned standard-library operator implementation that is usable under `msrv`. In a |
| /// `const` context that means const-stable, which the `NonZero` operator implementations are not. |
| fn has_nonzero_operator<'tcx>( |
| cx: &LateContext<'tcx>, |
| lhs: &'tcx Expr<'tcx>, |
| recv: &'tcx Expr<'tcx>, |
| nz_ty: Ty<'tcx>, |
| trait_id: DefId, |
| msrv: Msrv, |
| ) -> bool { |
| let lhs_ty = cx.typeck_results().expr_ty(lhs); |
| let ty::Adt(_, args) = nz_ty.kind() else { |
| return false; |
| }; |
| let inner_ty = args.type_at(0); |
| |
| // Primitive operators forward some reference operands, whereas the `NonZero` implementations |
| // do not. Require the exact written operand types so the replacement is guaranteed to resolve. |
| lhs_ty == inner_ty |
| && cx.typeck_results().expr_ty(recv) == nz_ty |
| && matches!(lhs_ty.kind(), ty::Uint(_)) |
| && implements_trait(cx, lhs_ty, trait_id, &[nz_ty.into()]) |
| && cx.tcx.non_blanket_impls_for_ty(trait_id, lhs_ty).any(|impl_id| { |
| let trait_ref = cx.tcx.impl_trait_ref(impl_id).instantiate_identity().skip_norm_wip(); |
| trait_ref.args.type_at(1) == nz_ty && msrv.is_stable_or_const_stable(cx, impl_id) |
| }) |
| } |
| |
| /// Checks whether `nz_ty` has an inherent method `name` taking nothing but `self` and returning |
| /// exactly `ret_ty`, which is usable under `msrv` (const-stable when in a `const` context, stable |
| /// otherwise). |
| fn has_matching_method<'tcx>( |
| cx: &LateContext<'tcx>, |
| nonzero_did: DefId, |
| nz_ty: Ty<'tcx>, |
| name: Symbol, |
| ret_ty: Ty<'tcx>, |
| msrv: Msrv, |
| ) -> bool { |
| cx.tcx.inherent_impls(nonzero_did).iter().any(|&impl_did| { |
| // The integer methods live in concrete `impl NonZero<u32>`-style blocks, so their |
| // signatures need no instantiation. Restricting to the impl matching the receiver also |
| // keeps signed-only methods off unsigned `NonZero`s and vice versa. |
| cx.tcx.type_of(impl_did).instantiate_identity().skip_norm_wip() == nz_ty |
| && cx |
| .tcx |
| .associated_items(impl_did) |
| .filter_by_name_unhygienic(name) |
| .any(|item| { |
| item.is_fn() && { |
| let sig = cx.tcx.fn_sig(item.def_id).instantiate_identity().skip_binder(); |
| sig.inputs().len() == 1 |
| && sig.output() == ret_ty |
| && msrv.is_stable_or_const_stable(cx, item.def_id) |
| } |
| }) |
| }) |
| } |