-
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
overhaul mut_mut
#15417
Changes from all commits
bb1081e
a4fba85
70be286
269870a
729d92c
c6971c2
4053c44
9a1b2bc
5546806
75b6860
dc534c4
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,49 +1,100 @@ | ||
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 | ||
/// Checks for instances of `mut mut` references. | ||
/// | ||
/// ### Why is this bad? | ||
/// Multiple `mut`s don't add anything meaningful to the | ||
/// source. This is either a copy'n'paste error, or it shows a fundamental | ||
/// misunderstanding of references. | ||
/// This is usually just a typo or a misunderstanding of how references work. | ||
/// | ||
/// ### 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 ...`" | ||
"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( | ||
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, | ||
"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 +105,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 +119,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) { | ||
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. (Not a problem, just record-keeping) One might think that it's an improvement to use But being that there aren't almost any occurences of |
||
return; | ||
} | ||
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, next) = e2.kind { | ||
(e, e2) = (e2, next); | ||
many_muts = true; | ||
} else { | ||
break; | ||
} | ||
} | ||
Comment on lines
+132
to
+142
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. Could you extract these two blocks into a function? The 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. You mean in order to share the peeling logic with the types case? But wouldn't that be impossible, given that we peel |
||
|
||
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 _`", | ||
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. is this message helpful enough? 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. Yes, I'd say that its helpful enough. What do you have in mind? 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. Well it now just states the fact, not really explaining why having double |
||
|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 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. An important property of Clippy is that we have an unique position and people learn from Clippy's output. So, they (ideally) come to Clippy with a learner mindset. With the suggestion, they'll assume that adding |
||
}, | ||
); | ||
} | ||
} | ||
|
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; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.