-
Notifications
You must be signed in to change notification settings - Fork 1.8k
overhaul mut_mut
#15417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
overhaul mut_mut
#15417
Changes from all commits
87ee2c5
eccb6a6
97213dc
bc6465a
203dd7b
9f1caef
6cfd467
fb9b526
50de9ef
c00a85d
dcb123e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,13 @@ | ||
use clippy_utils::diagnostics::{span_lint, span_lint_hir}; | ||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; | ||
use clippy_utils::higher; | ||
use rustc_hir::{self as hir, AmbigArg, intravisit}; | ||
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::declare_lint_pass; | ||
use rustc_session::impl_lint_pass; | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
|
@@ -16,34 +20,83 @@ declare_clippy_lint! { | |
/// | ||
/// ### Example | ||
/// ```no_run | ||
/// # let mut y = 1; | ||
/// let x = &mut &mut y; | ||
/// 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 ...`" | ||
} | ||
|
||
declare_lint_pass!(MutMut => [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 hir::TyKind::Ref(_, mty) = ty.kind | ||
&& mty.mutbl == hir::Mutability::Mut | ||
&& let hir::TyKind::Ref(_, mty) = mty.ty.kind | ||
&& mty.mutbl == hir::Mutability::Mut | ||
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()) | ||
{ | ||
span_lint( | ||
// 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)` | ||
if self.seen_tys.contains(&ty.hir_id) { | ||
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, | ||
"generally you want to avoid `&mut &mut _` if possible", | ||
"a type of form `&mut &mut _`", | ||
format!("remove the extra `&mut`{suffix}"), | ||
sugg.to_string(), | ||
applicability, | ||
); | ||
} | ||
} | ||
|
@@ -54,7 +107,7 @@ pub struct MutVisitor<'a, 'tcx> { | |
} | ||
|
||
impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> { | ||
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { | ||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { | ||
if expr.span.in_external_macro(self.cx.sess().source_map()) { | ||
return; | ||
} | ||
|
@@ -68,24 +121,60 @@ impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> { | |
// Let's ignore the generated code. | ||
intravisit::walk_expr(self, arg); | ||
intravisit::walk_expr(self, body); | ||
} else if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, e) = expr.kind { | ||
if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, _) = e.kind { | ||
span_lint_hir( | ||
} 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, | ||
"generally you want to avoid `&mut &mut _` if possible", | ||
"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, hir::Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() | ||
} 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()) | ||
{ | ||
span_lint_hir( | ||
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. Consider reborrowing", | ||
"this expression mutably borrows a mutable reference", | ||
|diag| { | ||
diag.span_suggestion(expr.span, "reborrow instead", sugg, applicability); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From my experience, not many people know about reborrowing -- maybe the message for the case that recommends it should link to some page that explains it? Admittedly this is less necessary now that we've got a structured suggestion; people can just search for the term separately if they're interested |
||
}, | ||
); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
//@aux-build:proc_macros.rs | ||
|
||
#![warn(clippy::mut_mut)] | ||
#![allow(unused)] | ||
#![allow( | ||
clippy::no_effect, | ||
clippy::uninlined_format_args, | ||
clippy::unnecessary_operation, | ||
clippy::needless_pass_by_ref_mut | ||
)] | ||
|
||
extern crate proc_macros; | ||
use proc_macros::{external, inline_macros}; | ||
|
||
fn fun(x: &mut u32) { | ||
//~^ mut_mut | ||
} | ||
|
||
fn less_fun(x: *mut *mut u32) { | ||
let y = x; | ||
} | ||
|
||
macro_rules! mut_ptr { | ||
($p:expr) => { | ||
&mut $p | ||
}; | ||
} | ||
|
||
#[allow(unused_mut, unused_variables)] | ||
#[inline_macros] | ||
fn main() { | ||
let mut x = &mut 1u32; | ||
//~^ mut_mut | ||
{ | ||
let mut y = &mut *x; | ||
//~^ mut_mut | ||
} | ||
|
||
{ | ||
let y: &mut u32 = &mut 2; | ||
//~^ mut_mut | ||
//~| mut_mut | ||
} | ||
|
||
{ | ||
let y: &mut u32 = &mut 2; | ||
//~^ mut_mut | ||
//~| mut_mut | ||
} | ||
|
||
let mut z = inline!(&mut $(&mut 3u32)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I stopped the lint from firing when inside macros, because the
should I try bringing the suggestion back, and if so, how? |
||
} | ||
|
||
fn issue939() { | ||
let array = [5, 6, 7, 8, 9]; | ||
let mut args = array.iter().skip(2); | ||
for &arg in &mut args { | ||
println!("{}", arg); | ||
} | ||
|
||
let args = &mut args; | ||
for arg in args { | ||
println!(":{}", arg); | ||
} | ||
} | ||
|
||
fn issue6922() { | ||
// do not lint from an external macro | ||
external!(let mut_mut_ty: &mut &mut u32 = &mut &mut 1u32;); | ||
} | ||
|
||
mod issue9035 { | ||
use std::fmt::Display; | ||
|
||
struct Foo<'a> { | ||
inner: &'a mut dyn Display, | ||
} | ||
|
||
impl Foo<'_> { | ||
fn foo(&mut self) { | ||
let hlp = &mut self.inner; | ||
bar(hlp); | ||
} | ||
} | ||
|
||
fn bar(_: &mut impl Display) {} | ||
} | ||
|
||
fn allow_works() { | ||
#[allow(clippy::mut_mut)] | ||
let _ = &mut &mut 1; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this message helpful enough?