| use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; |
| use clippy_utils::higher; |
| use clippy_utils::source::snippet_with_applicability; |
| use clippy_utils::sugg::Sugg; |
| use rustc_data_structures::fx::FxHashSet; |
| use rustc_errors::Applicability; |
| use rustc_hir::{self as hir, AmbigArg, BorrowKind, Expr, ExprKind, HirId, Mutability, TyKind, intravisit}; |
| use rustc_lint::{LateContext, LateLintPass, LintContext}; |
| use rustc_middle::ty; |
| use rustc_session::impl_lint_pass; |
| |
| declare_clippy_lint! { |
| /// ### What it does |
| /// Checks for instances of `mut mut` references. |
| /// |
| /// ### Why is this bad? |
| /// This is usually just a typo or a misunderstanding of how references work. |
| /// |
| /// ### Example |
| /// ```no_run |
| /// let x = &mut &mut 1; |
| /// |
| /// let mut x = &mut 1; |
| /// let y = &mut x; |
| /// |
| /// fn foo(x: &mut &mut u32) {} |
| /// ``` |
| /// Use instead |
| /// ```no_run |
| /// let x = &mut 1; |
| /// |
| /// let mut x = &mut 1; |
| /// let y = &mut *x; // reborrow |
| /// |
| /// fn foo(x: &mut u32) {} |
| /// ``` |
| #[clippy::version = "pre 1.29.0"] |
| pub MUT_MUT, |
| pedantic, |
| "usage of double mut-refs, e.g., `&mut &mut ...`" |
| } |
| |
| impl_lint_pass!(MutMut => [MUT_MUT]); |
| |
| #[derive(Default)] |
| pub(crate) struct MutMut { |
| seen_tys: FxHashSet<HirId>, |
| } |
| |
| impl<'tcx> LateLintPass<'tcx> for MutMut { |
| fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { |
| intravisit::walk_block(&mut MutVisitor { cx }, block); |
| } |
| |
| fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'_, AmbigArg>) { |
| if let TyKind::Ref(_, mty) = ty.kind |
| && mty.mutbl == Mutability::Mut |
| && let TyKind::Ref(_, mty2) = mty.ty.kind |
| && mty2.mutbl == Mutability::Mut |
| && !ty.span.in_external_macro(cx.sess().source_map()) |
| { |
| if self.seen_tys.contains(&ty.hir_id) { |
| // we have 2+ `&mut`s, e.g., `&mut &mut &mut x` |
| // and we have already flagged on the outermost `&mut &mut (&mut x)`, |
| // so don't flag the inner `&mut &mut (x)` |
| return; |
| } |
| |
| // if there is an even longer chain, like `&mut &mut &mut x`, suggest peeling off |
| // all extra ones at once |
| let (mut t, mut t2) = (mty.ty, mty2.ty); |
| let mut many_muts = false; |
| loop { |
| // this should allow us to remember all the nested types, so that the `contains` |
| // above fails faster |
| self.seen_tys.insert(t.hir_id); |
| if let TyKind::Ref(_, next) = t2.kind |
| && next.mutbl == Mutability::Mut |
| { |
| (t, t2) = (t2, next.ty); |
| many_muts = true; |
| } else { |
| break; |
| } |
| } |
| |
| let mut applicability = Applicability::MaybeIncorrect; |
| let sugg = snippet_with_applicability(cx.sess(), t.span, "..", &mut applicability); |
| let suffix = if many_muts { "s" } else { "" }; |
| span_lint_and_sugg( |
| cx, |
| MUT_MUT, |
| ty.span, |
| "a type of form `&mut &mut _`", |
| format!("remove the extra `&mut`{suffix}"), |
| sugg.to_string(), |
| applicability, |
| ); |
| } |
| } |
| } |
| |
| pub struct MutVisitor<'a, 'tcx> { |
| cx: &'a LateContext<'tcx>, |
| } |
| |
| impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> { |
| fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { |
| if expr.span.in_external_macro(self.cx.sess().source_map()) { |
| return; |
| } |
| |
| if let Some(higher::ForLoop { arg, body, .. }) = higher::ForLoop::hir(expr) { |
| // A `for` loop lowers to: |
| // ```rust |
| // match ::std::iter::Iterator::next(&mut iter) { |
| // // ^^^^ |
| // ``` |
| // Let's ignore the generated code. |
| intravisit::walk_expr(self, arg); |
| intravisit::walk_expr(self, body); |
| } else if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e) = expr.kind { |
| if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e2) = e.kind { |
| if !expr.span.eq_ctxt(e.span) { |
| return; |
| } |
| |
| // if there is an even longer chain, like `&mut &mut &mut x`, suggest peeling off |
| // all extra ones at once |
| let (mut e, mut e2) = (e, e2); |
| let mut many_muts = false; |
| loop { |
| if !e.span.eq_ctxt(e2.span) { |
| return; |
| } |
| if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, next) = e2.kind { |
| (e, e2) = (e2, next); |
| many_muts = true; |
| } else { |
| break; |
| } |
| } |
| |
| let mut applicability = Applicability::MaybeIncorrect; |
| let sugg = Sugg::hir_with_applicability(self.cx, e, "..", &mut applicability); |
| let suffix = if many_muts { "s" } else { "" }; |
| span_lint_hir_and_then( |
| self.cx, |
| MUT_MUT, |
| expr.hir_id, |
| expr.span, |
| "an expression of form `&mut &mut _`", |
| |diag| { |
| diag.span_suggestion( |
| expr.span, |
| format!("remove the extra `&mut`{suffix}"), |
| sugg, |
| applicability, |
| ); |
| }, |
| ); |
| } else if let ty::Ref(_, ty, Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() |
| && ty.peel_refs().is_sized(self.cx.tcx, self.cx.typing_env()) |
| { |
| let mut applicability = Applicability::MaybeIncorrect; |
| let sugg = Sugg::hir_with_applicability(self.cx, e, "..", &mut applicability).mut_addr_deref(); |
| span_lint_hir_and_then( |
| self.cx, |
| MUT_MUT, |
| expr.hir_id, |
| expr.span, |
| "this expression mutably borrows a mutable reference", |
| |diag| { |
| diag.span_suggestion(expr.span, "reborrow instead", sugg, applicability); |
| }, |
| ); |
| } |
| } |
| } |
| } |