Skip to content

Commit 98a92bf

Browse files
authored
overhaul mut_mut (#15417)
This PR: - adds structured suggestions to all 3 lint scenarios - adds more examples of linted patterns, and recommended fixes - moves some test cases to `_unfixable.rs`, to allow running rustfix on the main file - stops the lint from firing multiple times on types like `&mut &mut &mut T` Open questions: - I'd like to add a note explaining why chained `&mut` are useless.. but can't really come up with something consice. I very much don't like the one in the docs -- it's a bit too condescending imo. - see comments in the diff for more I do realize that the PR ended up being quite wide-scoped, but that's primarily because once I added structured suggestions to one of the lint cases, `ui_test` started complaining about warnings remaining after the rustfix run, which of course had to with the fact that the other two lint cases didn't actually fix the code they linted, only warned. I _guess_ `ui_test` could be smarter about this, by understanding that if a warning was created without a suggestion, then that warning will still remain in the fixed file. But oh well. changelog: [`mut_mut`]: add structured suggestions, improve docs fixes #13021
2 parents dfd8b2d + dc534c4 commit 98a92bf

File tree

7 files changed

+312
-69
lines changed

7 files changed

+312
-69
lines changed

clippy_lints/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
482482
store.register_late_pass(|_| Box::new(needless_for_each::NeedlessForEach));
483483
store.register_late_pass(|_| Box::new(misc::LintPass));
484484
store.register_late_pass(|_| Box::new(eta_reduction::EtaReduction));
485-
store.register_late_pass(|_| Box::new(mut_mut::MutMut));
485+
store.register_late_pass(|_| Box::new(mut_mut::MutMut::default()));
486486
store.register_late_pass(|_| Box::new(unnecessary_mut_passed::UnnecessaryMutPassed));
487487
store.register_late_pass(|_| Box::<significant_drop_tightening::SignificantDropTightening<'_>>::default());
488488
store.register_late_pass(|_| Box::new(len_zero::LenZero));

clippy_lints/src/mut_mut.rs

Lines changed: 111 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,100 @@
1-
use clippy_utils::diagnostics::{span_lint, span_lint_hir};
1+
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
22
use clippy_utils::higher;
3-
use rustc_hir::{self as hir, AmbigArg, intravisit};
3+
use clippy_utils::source::snippet_with_applicability;
4+
use clippy_utils::sugg::Sugg;
5+
use rustc_data_structures::fx::FxHashSet;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::{self as hir, AmbigArg, BorrowKind, Expr, ExprKind, HirId, Mutability, TyKind, intravisit};
48
use rustc_lint::{LateContext, LateLintPass, LintContext};
59
use rustc_middle::ty;
6-
use rustc_session::declare_lint_pass;
10+
use rustc_session::impl_lint_pass;
711

812
declare_clippy_lint! {
913
/// ### What it does
1014
/// Checks for instances of `mut mut` references.
1115
///
1216
/// ### Why is this bad?
13-
/// Multiple `mut`s don't add anything meaningful to the
14-
/// source. This is either a copy'n'paste error, or it shows a fundamental
15-
/// misunderstanding of references.
17+
/// This is usually just a typo or a misunderstanding of how references work.
1618
///
1719
/// ### Example
1820
/// ```no_run
19-
/// # let mut y = 1;
20-
/// let x = &mut &mut y;
21+
/// let x = &mut &mut 1;
22+
///
23+
/// let mut x = &mut 1;
24+
/// let y = &mut x;
25+
///
26+
/// fn foo(x: &mut &mut u32) {}
27+
/// ```
28+
/// Use instead
29+
/// ```no_run
30+
/// let x = &mut 1;
31+
///
32+
/// let mut x = &mut 1;
33+
/// let y = &mut *x; // reborrow
34+
///
35+
/// fn foo(x: &mut u32) {}
2136
/// ```
2237
#[clippy::version = "pre 1.29.0"]
2338
pub MUT_MUT,
2439
pedantic,
25-
"usage of double-mut refs, e.g., `&mut &mut ...`"
40+
"usage of double mut-refs, e.g., `&mut &mut ...`"
2641
}
2742

28-
declare_lint_pass!(MutMut => [MUT_MUT]);
43+
impl_lint_pass!(MutMut => [MUT_MUT]);
44+
45+
#[derive(Default)]
46+
pub(crate) struct MutMut {
47+
seen_tys: FxHashSet<HirId>,
48+
}
2949

3050
impl<'tcx> LateLintPass<'tcx> for MutMut {
3151
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
3252
intravisit::walk_block(&mut MutVisitor { cx }, block);
3353
}
3454

3555
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'_, AmbigArg>) {
36-
if let hir::TyKind::Ref(_, mty) = ty.kind
37-
&& mty.mutbl == hir::Mutability::Mut
38-
&& let hir::TyKind::Ref(_, mty) = mty.ty.kind
39-
&& mty.mutbl == hir::Mutability::Mut
56+
if let TyKind::Ref(_, mty) = ty.kind
57+
&& mty.mutbl == Mutability::Mut
58+
&& let TyKind::Ref(_, mty2) = mty.ty.kind
59+
&& mty2.mutbl == Mutability::Mut
4060
&& !ty.span.in_external_macro(cx.sess().source_map())
4161
{
42-
span_lint(
62+
if self.seen_tys.contains(&ty.hir_id) {
63+
// we have 2+ `&mut`s, e.g., `&mut &mut &mut x`
64+
// and we have already flagged on the outermost `&mut &mut (&mut x)`,
65+
// so don't flag the inner `&mut &mut (x)`
66+
return;
67+
}
68+
69+
// if there is an even longer chain, like `&mut &mut &mut x`, suggest peeling off
70+
// all extra ones at once
71+
let (mut t, mut t2) = (mty.ty, mty2.ty);
72+
let mut many_muts = false;
73+
loop {
74+
// this should allow us to remember all the nested types, so that the `contains`
75+
// above fails faster
76+
self.seen_tys.insert(t.hir_id);
77+
if let TyKind::Ref(_, next) = t2.kind
78+
&& next.mutbl == Mutability::Mut
79+
{
80+
(t, t2) = (t2, next.ty);
81+
many_muts = true;
82+
} else {
83+
break;
84+
}
85+
}
86+
87+
let mut applicability = Applicability::MaybeIncorrect;
88+
let sugg = snippet_with_applicability(cx.sess(), t.span, "..", &mut applicability);
89+
let suffix = if many_muts { "s" } else { "" };
90+
span_lint_and_sugg(
4391
cx,
4492
MUT_MUT,
4593
ty.span,
46-
"generally you want to avoid `&mut &mut _` if possible",
94+
"a type of form `&mut &mut _`",
95+
format!("remove the extra `&mut`{suffix}"),
96+
sugg.to_string(),
97+
applicability,
4798
);
4899
}
49100
}
@@ -54,7 +105,7 @@ pub struct MutVisitor<'a, 'tcx> {
54105
}
55106

56107
impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> {
57-
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
108+
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
58109
if expr.span.in_external_macro(self.cx.sess().source_map()) {
59110
return;
60111
}
@@ -68,24 +119,60 @@ impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> {
68119
// Let's ignore the generated code.
69120
intravisit::walk_expr(self, arg);
70121
intravisit::walk_expr(self, body);
71-
} else if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, e) = expr.kind {
72-
if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, _) = e.kind {
73-
span_lint_hir(
122+
} else if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e) = expr.kind {
123+
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e2) = e.kind {
124+
if !expr.span.eq_ctxt(e.span) {
125+
return;
126+
}
127+
128+
// if there is an even longer chain, like `&mut &mut &mut x`, suggest peeling off
129+
// all extra ones at once
130+
let (mut e, mut e2) = (e, e2);
131+
let mut many_muts = false;
132+
loop {
133+
if !e.span.eq_ctxt(e2.span) {
134+
return;
135+
}
136+
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, next) = e2.kind {
137+
(e, e2) = (e2, next);
138+
many_muts = true;
139+
} else {
140+
break;
141+
}
142+
}
143+
144+
let mut applicability = Applicability::MaybeIncorrect;
145+
let sugg = Sugg::hir_with_applicability(self.cx, e, "..", &mut applicability);
146+
let suffix = if many_muts { "s" } else { "" };
147+
span_lint_hir_and_then(
74148
self.cx,
75149
MUT_MUT,
76150
expr.hir_id,
77151
expr.span,
78-
"generally you want to avoid `&mut &mut _` if possible",
152+
"an expression of form `&mut &mut _`",
153+
|diag| {
154+
diag.span_suggestion(
155+
expr.span,
156+
format!("remove the extra `&mut`{suffix}"),
157+
sugg,
158+
applicability,
159+
);
160+
},
79161
);
80-
} else if let ty::Ref(_, ty, hir::Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind()
162+
} else if let ty::Ref(_, ty, Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind()
81163
&& ty.peel_refs().is_sized(self.cx.tcx, self.cx.typing_env())
82164
{
83-
span_lint_hir(
165+
let mut applicability = Applicability::MaybeIncorrect;
166+
let sugg = Sugg::hir_with_applicability(self.cx, e, "..", &mut applicability).mut_addr_deref();
167+
span_lint_hir_and_then(
84168
self.cx,
85169
MUT_MUT,
86170
expr.hir_id,
87171
expr.span,
88-
"this expression mutably borrows a mutable reference. Consider reborrowing",
172+
"this expression mutably borrows a mutable reference",
173+
|diag| {
174+
diag.span_suggestion(expr.span, "reborrow instead", sugg, applicability);
175+
},
89176
);
90177
}
91178
}

tests/ui/mut_mut.fixed

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//@aux-build:proc_macros.rs
2+
3+
#![warn(clippy::mut_mut)]
4+
#![allow(unused)]
5+
#![allow(
6+
clippy::no_effect,
7+
clippy::uninlined_format_args,
8+
clippy::unnecessary_operation,
9+
clippy::needless_pass_by_ref_mut
10+
)]
11+
12+
extern crate proc_macros;
13+
use proc_macros::{external, inline_macros};
14+
15+
fn fun(x: &mut u32) {
16+
//~^ mut_mut
17+
}
18+
19+
fn less_fun(x: *mut *mut u32) {
20+
let y = x;
21+
}
22+
23+
macro_rules! mut_ptr {
24+
($p:expr) => {
25+
&mut $p
26+
};
27+
}
28+
29+
#[allow(unused_mut, unused_variables)]
30+
#[inline_macros]
31+
fn main() {
32+
let mut x = &mut 1u32;
33+
//~^ mut_mut
34+
{
35+
let mut y = &mut *x;
36+
//~^ mut_mut
37+
}
38+
39+
{
40+
let y: &mut u32 = &mut 2;
41+
//~^ mut_mut
42+
//~| mut_mut
43+
}
44+
45+
{
46+
let y: &mut u32 = &mut 2;
47+
//~^ mut_mut
48+
//~| mut_mut
49+
}
50+
51+
let mut z = inline!(&mut $(&mut 3u32));
52+
}
53+
54+
fn issue939() {
55+
let array = [5, 6, 7, 8, 9];
56+
let mut args = array.iter().skip(2);
57+
for &arg in &mut args {
58+
println!("{}", arg);
59+
}
60+
61+
let args = &mut args;
62+
for arg in args {
63+
println!(":{}", arg);
64+
}
65+
}
66+
67+
fn issue6922() {
68+
// do not lint from an external macro
69+
external!(let mut_mut_ty: &mut &mut u32 = &mut &mut 1u32;);
70+
}
71+
72+
mod issue9035 {
73+
use std::fmt::Display;
74+
75+
struct Foo<'a> {
76+
inner: &'a mut dyn Display,
77+
}
78+
79+
impl Foo<'_> {
80+
fn foo(&mut self) {
81+
let hlp = &mut self.inner;
82+
bar(hlp);
83+
}
84+
}
85+
86+
fn bar(_: &mut impl Display) {}
87+
}
88+
89+
fn allow_works() {
90+
#[allow(clippy::mut_mut)]
91+
let _ = &mut &mut 1;
92+
}

tests/ui/mut_mut.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@
1212
extern crate proc_macros;
1313
use proc_macros::{external, inline_macros};
1414

15-
fn fun(x: &mut &mut u32) -> bool {
15+
fn fun(x: &mut &mut u32) {
1616
//~^ mut_mut
17-
**x > 0
1817
}
1918

2019
fn less_fun(x: *mut *mut u32) {
@@ -37,23 +36,19 @@ fn main() {
3736
//~^ mut_mut
3837
}
3938

40-
if fun(x) {
39+
{
4140
let y: &mut &mut u32 = &mut &mut 2;
4241
//~^ mut_mut
4342
//~| mut_mut
44-
**y + **x;
4543
}
4644

47-
if fun(x) {
45+
{
4846
let y: &mut &mut &mut u32 = &mut &mut &mut 2;
4947
//~^ mut_mut
5048
//~| mut_mut
51-
//~| mut_mut
52-
***y + **x;
5349
}
5450

5551
let mut z = inline!(&mut $(&mut 3u32));
56-
//~^ mut_mut
5752
}
5853

5954
fn issue939() {

0 commit comments

Comments
 (0)