Skip to content

Commit 0595b19

Browse files
committed
Fix manual_inspect to consider mutability
1 parent 19e305b commit 0595b19

File tree

6 files changed

+475
-32
lines changed

6 files changed

+475
-32
lines changed

clippy_lints/src/dereference.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
270270
RefOp::Deref if use_cx.same_ctxt => {
271271
let use_node = use_cx.use_node(cx);
272272
let sub_ty = typeck.expr_ty(sub_expr);
273-
if let ExprUseNode::FieldAccess(name) = use_node
273+
if let ExprUseNode::FieldAccess(_, name) = use_node
274274
&& !use_cx.moved_before_use
275275
&& !ty_contains_field(sub_ty, name.name)
276276
{
@@ -339,7 +339,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
339339
TyCoercionStability::for_defined_ty(cx, ty, use_node.is_return())
340340
});
341341
let can_auto_borrow = match use_node {
342-
ExprUseNode::FieldAccess(_)
342+
ExprUseNode::FieldAccess(_, _)
343343
if !use_cx.moved_before_use && matches!(sub_expr.kind, ExprKind::Field(..)) =>
344344
{
345345
// `DerefMut` will not be automatically applied to `ManuallyDrop<_>`
@@ -350,7 +350,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
350350
// deref through `ManuallyDrop<_>` will not compile.
351351
!adjust_derefs_manually_drop(use_cx.adjustments, expr_ty)
352352
},
353-
ExprUseNode::Callee | ExprUseNode::FieldAccess(_) if !use_cx.moved_before_use => true,
353+
ExprUseNode::Callee | ExprUseNode::FieldAccess(_, _) if !use_cx.moved_before_use => true,
354354
ExprUseNode::MethodArg(hir_id, _, 0) if !use_cx.moved_before_use => {
355355
// Check for calls to trait methods where the trait is implemented
356356
// on a reference.
@@ -438,7 +438,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
438438
count: deref_count - required_refs,
439439
msg,
440440
stability,
441-
for_field_access: if let ExprUseNode::FieldAccess(name) = use_node
441+
for_field_access: if let ExprUseNode::FieldAccess(_, name) = use_node
442442
&& !use_cx.moved_before_use
443443
{
444444
Some(name.name)

clippy_lints/src/methods/manual_inspect.rs

+53-10
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use clippy_utils::visitors::{for_each_expr, for_each_expr_without_closures};
66
use clippy_utils::{ExprUseNode, expr_use_ctxt, is_diag_item_method, is_diag_trait_item, path_to_local_id};
77
use core::ops::ControlFlow;
88
use rustc_errors::Applicability;
9-
use rustc_hir::{BindingMode, BorrowKind, ByRef, ClosureKind, Expr, ExprKind, Mutability, Node, PatKind};
9+
use rustc_hir::{BindingMode, BorrowKind, ByRef, ClosureKind, Expr, ExprKind, HirId, Mutability, Node, PatKind};
1010
use rustc_lint::LateContext;
1111
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
1212
use rustc_span::{DUMMY_SP, Span, Symbol, sym};
@@ -34,6 +34,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
3434
{
3535
let mut requires_copy = false;
3636
let mut requires_deref = false;
37+
let mut has_mut_use = false;
3738

3839
// The number of unprocessed return expressions.
3940
let mut ret_count = 0u32;
@@ -47,7 +48,8 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
4748
// Nested closures don't need to treat returns specially.
4849
let _: Option<!> = for_each_expr(cx, cx.tcx.hir().body(c.body).value, |e| {
4950
if path_to_local_id(e, arg_id) {
50-
let (kind, same_ctxt) = check_use(cx, e);
51+
let (kind, mutbl, same_ctxt) = check_use(cx, e);
52+
has_mut_use |= mutbl.is_mut();
5153
match (kind, same_ctxt && e.span.ctxt() == ctxt) {
5254
(_, false) | (UseKind::Deref | UseKind::Return(..), true) => {
5355
requires_copy = true;
@@ -65,7 +67,8 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
6567
} else if matches!(e.kind, ExprKind::Ret(_)) {
6668
ret_count += 1;
6769
} else if path_to_local_id(e, arg_id) {
68-
let (kind, same_ctxt) = check_use(cx, e);
70+
let (kind, mutbl, same_ctxt) = check_use(cx, e);
71+
has_mut_use |= mutbl.is_mut();
6972
match (kind, same_ctxt && e.span.ctxt() == ctxt) {
7073
(UseKind::Return(..), false) => {
7174
return ControlFlow::Break(());
@@ -161,6 +164,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
161164
&& (!requires_copy || cx.type_is_copy_modulo_regions(arg_ty))
162165
// This case could be handled, but a fair bit of care would need to be taken.
163166
&& (!requires_deref || arg_ty.is_freeze(cx.tcx, cx.typing_env()))
167+
&& !has_mut_use
164168
{
165169
if requires_deref {
166170
edits.push((param.span.shrink_to_lo(), "&".into()));
@@ -207,36 +211,75 @@ enum UseKind<'tcx> {
207211
FieldAccess(Symbol, &'tcx Expr<'tcx>),
208212
}
209213

210-
/// Checks how the value is used, and whether it was used in the same `SyntaxContext`.
211-
fn check_use<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (UseKind<'tcx>, bool) {
214+
/// Checks how the value is used, mutability, and whether it was used in the same `SyntaxContext`.
215+
fn check_use<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (UseKind<'tcx>, Mutability, bool) {
212216
let use_cx = expr_use_ctxt(cx, e);
217+
let mutbl = use_mutability(cx, e.hir_id);
213218
if use_cx
214219
.adjustments
215220
.first()
216221
.is_some_and(|a| matches!(a.kind, Adjust::Deref(_)))
217222
{
218-
return (UseKind::AutoBorrowed, use_cx.same_ctxt);
223+
return (UseKind::AutoBorrowed, mutbl, use_cx.same_ctxt);
219224
}
220225
let res = match use_cx.use_node(cx) {
221226
ExprUseNode::Return(_) => {
222227
if let ExprKind::Ret(Some(e)) = use_cx.node.expect_expr().kind {
223228
UseKind::Return(e.span)
224229
} else {
225-
return (UseKind::Return(DUMMY_SP), false);
230+
return (UseKind::Return(DUMMY_SP), mutbl, false);
226231
}
227232
},
228-
ExprUseNode::FieldAccess(name) => UseKind::FieldAccess(name.name, use_cx.node.expect_expr()),
233+
ExprUseNode::FieldAccess(_, name) => UseKind::FieldAccess(name.name, use_cx.node.expect_expr()),
229234
ExprUseNode::Callee | ExprUseNode::MethodArg(_, _, 0)
230235
if use_cx
231236
.adjustments
232237
.first()
233-
.is_some_and(|a| matches!(a.kind, Adjust::Borrow(AutoBorrow::Ref(AutoBorrowMutability::Not)))) =>
238+
.is_some_and(|a| matches!(a.kind, Adjust::Borrow(AutoBorrow::Ref(_)))) =>
234239
{
235240
UseKind::AutoBorrowed
236241
},
237242
ExprUseNode::Callee | ExprUseNode::MethodArg(_, _, 0) => UseKind::WillAutoDeref,
238243
ExprUseNode::AddrOf(BorrowKind::Ref, _) => UseKind::Borrowed(use_cx.node.expect_expr().span),
239244
_ => UseKind::Deref,
240245
};
241-
(res, use_cx.same_ctxt)
246+
(res, mutbl, use_cx.same_ctxt)
247+
}
248+
249+
fn use_mutability(cx: &LateContext<'_>, expr_id: HirId) -> Mutability {
250+
let adjusted = |expr: &Expr<'_>| -> Mutability {
251+
let adj = cx.typeck_results().expr_adjustments(expr);
252+
if let Some(Adjust::Borrow(AutoBorrow::Ref(mutbl))) = adj.last().map(|adj| &adj.kind) {
253+
(*mutbl).into()
254+
} else {
255+
Mutability::Not
256+
}
257+
};
258+
259+
let mut last_child = None;
260+
261+
for (_, node) in cx.tcx.hir().parent_iter(expr_id) {
262+
if let Node::Expr(expr) = node {
263+
match expr.kind {
264+
ExprKind::AddrOf(_, mutbl, _) => return mutbl,
265+
ExprKind::MethodCall(_, self_arg, _, _) => return adjusted(self_arg),
266+
ExprKind::Call(f, args) => return adjusted(args.iter().find(|arg| arg.hir_id == expr_id).unwrap_or(f)),
267+
ExprKind::Field(field, _) | ExprKind::Index(field, _, _) => last_child = Some(field.hir_id),
268+
ExprKind::Assign(lhs, _, _) | ExprKind::AssignOp(_, lhs, _) => {
269+
let is_lhs = match lhs.kind {
270+
ExprKind::Field(child, _) | ExprKind::Index(child, _, _) => {
271+
last_child.is_some_and(|field_id| field_id == child.hir_id)
272+
},
273+
_ => false,
274+
};
275+
if is_lhs {
276+
return Mutability::Mut;
277+
}
278+
return Mutability::Not;
279+
},
280+
_ => {},
281+
}
282+
}
283+
}
284+
Mutability::Not
242285
}

clippy_utils/src/lib.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -2753,7 +2753,7 @@ impl<'tcx> ExprUseCtxt<'tcx> {
27532753
.position(|arg| arg.hir_id == self.child_id)
27542754
.map_or(0, |i| i + 1),
27552755
),
2756-
ExprKind::Field(_, name) => ExprUseNode::FieldAccess(name),
2756+
ExprKind::Field(_, name) => ExprUseNode::FieldAccess(use_expr.hir_id, name),
27572757
ExprKind::AddrOf(kind, mutbl, _) => ExprUseNode::AddrOf(kind, mutbl),
27582758
_ => ExprUseNode::Other,
27592759
},
@@ -2763,6 +2763,7 @@ impl<'tcx> ExprUseCtxt<'tcx> {
27632763
}
27642764

27652765
/// The node which consumes a value.
2766+
#[derive(Debug)]
27662767
pub enum ExprUseNode<'tcx> {
27672768
/// Assignment to, or initializer for, a local
27682769
LetStmt(&'tcx LetStmt<'tcx>),
@@ -2779,7 +2780,7 @@ pub enum ExprUseNode<'tcx> {
27792780
/// The callee of a function call.
27802781
Callee,
27812782
/// Access of a field.
2782-
FieldAccess(Ident),
2783+
FieldAccess(HirId, Ident),
27832784
/// Borrow expression.
27842785
AddrOf(ast::BorrowKind, Mutability),
27852786
Other,

tests/ui/manual_inspect.fixed

+148-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
#![warn(clippy::manual_inspect)]
2-
#![allow(clippy::no_effect, clippy::op_ref)]
2+
#![allow(
3+
clippy::no_effect,
4+
clippy::op_ref,
5+
clippy::explicit_auto_deref,
6+
clippy::needless_borrow
7+
)]
38

49
fn main() {
510
let _ = Some(0).inspect(|&x| {
@@ -172,3 +177,145 @@ fn main() {
172177
});
173178
}
174179
}
180+
181+
fn issue_13185() {
182+
struct T(u32);
183+
184+
impl T {
185+
fn do_immut(&self) {
186+
println!("meow~");
187+
}
188+
189+
fn do_immut2(&self, other: &T) {
190+
println!("meow~");
191+
}
192+
193+
fn do_mut(&mut self) {
194+
self.0 += 514;
195+
}
196+
197+
fn do_mut2(&mut self, other: &mut T) {
198+
self.0 += 114;
199+
other.0 += 514;
200+
}
201+
}
202+
203+
_ = Some(T(114)).as_mut().inspect(|t| {
204+
t.0 + 514;
205+
});
206+
207+
_ = Some(T(114)).as_mut().map(|t| {
208+
t.0 = 514;
209+
t
210+
});
211+
212+
_ = Some(T(114)).as_mut().map(|t| {
213+
t.0 += 514;
214+
t
215+
});
216+
217+
// FIXME: It's better to lint this case
218+
_ = Some(T(114)).as_mut().map(|t| {
219+
let indirect = t;
220+
indirect.0 + 514;
221+
indirect
222+
});
223+
224+
_ = Some(T(114)).as_mut().map(|t| {
225+
let indirect = t;
226+
indirect.0 += 514;
227+
indirect
228+
});
229+
230+
_ = Some(T(114)).as_mut().map(|t| {
231+
t.do_mut();
232+
t
233+
});
234+
235+
_ = Some(T(114)).as_mut().map(|t| {
236+
T(514).do_mut2(t);
237+
t
238+
});
239+
240+
_ = Some(T(114)).as_mut().inspect(|t| {
241+
t.do_immut();
242+
});
243+
244+
_ = Some(T(114)).as_mut().inspect(|t| {
245+
t.do_immut2(t);
246+
});
247+
248+
_ = Some(T(114)).as_mut().map(|t| {
249+
T::do_mut(t);
250+
t
251+
});
252+
253+
_ = Some(T(114)).as_mut().inspect(|t| {
254+
T::do_immut(t);
255+
});
256+
257+
// FIXME: It's better to lint this case
258+
_ = Some(T(114)).as_mut().map(|t| {
259+
let indirect = t;
260+
indirect.do_immut();
261+
indirect
262+
});
263+
264+
// FIXME: It's better to lint this case
265+
_ = Some(T(114)).as_mut().map(|t| {
266+
(&*t).do_immut();
267+
t
268+
});
269+
270+
// Array element access
271+
272+
let mut sample = Some([1919, 810]);
273+
_ = sample.as_mut().map(|t| {
274+
t[1] += 1;
275+
t
276+
});
277+
278+
_ = sample.as_mut().inspect(|t| {
279+
let mut a = 1;
280+
a += t[1]; // immut
281+
});
282+
283+
// Nested fields access
284+
struct N((T, T), [T; 2]);
285+
286+
let mut sample = Some(N((T(114), T(514)), [T(1919), T(810)]));
287+
288+
_ = sample.as_mut().map(|n| {
289+
n.0.0.do_mut();
290+
n
291+
});
292+
293+
_ = sample.as_mut().inspect(|t| {
294+
t.0.0.do_immut();
295+
});
296+
297+
_ = sample.as_mut().map(|t| {
298+
T::do_mut(&mut t.0.0);
299+
t
300+
});
301+
302+
_ = sample.as_mut().inspect(|t| {
303+
T::do_immut(&t.0.0);
304+
});
305+
306+
// FnMut
307+
let mut state = 1;
308+
let immut_fn = Some(|| {
309+
println!("meow");
310+
});
311+
let mut mut_fn = Some(|| {
312+
state += 1;
313+
});
314+
immut_fn.inspect(|f| {
315+
f();
316+
});
317+
mut_fn.as_mut().map(|f| {
318+
f();
319+
f
320+
});
321+
}

0 commit comments

Comments
 (0)