-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathmut_mut.rs
More file actions
180 lines (168 loc) · 6.49 KB
/
mut_mut.rs
File metadata and controls
180 lines (168 loc) · 6.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
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);
},
);
}
}
}
}