Skip to content

Commit ff22ee9

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

File tree

6 files changed

+339
-33
lines changed

6 files changed

+339
-33
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

+14-9
Original file line numberDiff line numberDiff line change
@@ -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,37 @@ 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_cx.use_mutability(cx);
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)
242247
}

clippy_utils/src/lib.rs

+44-4
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ use std::iter::{once, repeat};
9292
use std::sync::{Mutex, MutexGuard, OnceLock};
9393

9494
use itertools::Itertools;
95-
use rustc_ast::ast::{self, LitKind, RangeLimits};
95+
use rustc_ast::ast::{self, BinOpKind, LitKind, RangeLimits};
9696
use rustc_data_structures::fx::FxHashMap;
9797
use rustc_data_structures::packed::Pu128;
9898
use rustc_data_structures::unhash::UnhashMap;
@@ -2753,16 +2753,46 @@ 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),
2758+
ExprKind::Assign(lhs, rhs, _) => {
2759+
debug_assert!(lhs.hir_id == self.child_id || rhs.hir_id == self.child_id);
2760+
#[allow(clippy::bool_to_int_with_if)]
2761+
let idx = if lhs.hir_id == self.child_id { 0 } else { 1 };
2762+
ExprUseNode::Assign(idx)
2763+
},
2764+
ExprKind::AssignOp(op, lhs, rhs) => {
2765+
debug_assert!(lhs.hir_id == self.child_id || rhs.hir_id == self.child_id);
2766+
#[allow(clippy::bool_to_int_with_if)]
2767+
let idx = if lhs.hir_id == self.child_id { 0 } else { 1 };
2768+
ExprUseNode::AssignOp(op.node, idx)
2769+
},
27582770
_ => ExprUseNode::Other,
27592771
},
27602772
_ => ExprUseNode::Other,
27612773
}
27622774
}
2775+
2776+
pub fn use_mutability(&self, cx: &LateContext<'tcx>) -> Mutability {
2777+
match self.use_node(cx) {
2778+
ExprUseNode::FieldAccess(parent, _) => {
2779+
let parent = cx.tcx.hir().expect_expr(parent);
2780+
expr_use_ctxt(cx, parent).use_mutability(cx)
2781+
},
2782+
ExprUseNode::AssignOp(_, 0) | ExprUseNode::Assign(0) => Mutability::Mut,
2783+
ExprUseNode::AddrOf(_, mutbl) => mutbl,
2784+
ExprUseNode::FnArg(_, _) | ExprUseNode::MethodArg(_, _, _) => {
2785+
let child_expr = cx.tcx.hir().expect_expr(self.child_id);
2786+
let ty = cx.typeck_results().expr_ty_adjusted(child_expr);
2787+
ty.ref_mutability().unwrap_or(Mutability::Not)
2788+
},
2789+
_ => Mutability::Not,
2790+
}
2791+
}
27632792
}
27642793

27652794
/// The node which consumes a value.
2795+
#[derive(Debug)]
27662796
pub enum ExprUseNode<'tcx> {
27672797
/// Assignment to, or initializer for, a local
27682798
LetStmt(&'tcx LetStmt<'tcx>),
@@ -2779,9 +2809,13 @@ pub enum ExprUseNode<'tcx> {
27792809
/// The callee of a function call.
27802810
Callee,
27812811
/// Access of a field.
2782-
FieldAccess(Ident),
2812+
FieldAccess(HirId, Ident),
27832813
/// Borrow expression.
27842814
AddrOf(ast::BorrowKind, Mutability),
2815+
/// Assignment.
2816+
Assign(usize),
2817+
/// Assignment with an operator.
2818+
AssignOp(BinOpKind, usize),
27852819
Other,
27862820
}
27872821
impl<'tcx> ExprUseNode<'tcx> {
@@ -2859,7 +2893,13 @@ impl<'tcx> ExprUseNode<'tcx> {
28592893
ty: sig.input(i),
28602894
})
28612895
},
2862-
Self::LetStmt(_) | Self::FieldAccess(..) | Self::Callee | Self::Other | Self::AddrOf(..) => None,
2896+
Self::LetStmt(_)
2897+
| Self::FieldAccess(..)
2898+
| Self::Callee
2899+
| Self::Other
2900+
| Self::AddrOf(..)
2901+
| Self::Assign(_)
2902+
| Self::AssignOp(..) => None,
28632903
}
28642904
}
28652905
}

tests/ui/manual_inspect.fixed

+99-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,96 @@ 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_mut(&mut self) {
190+
self.0 += 514;
191+
}
192+
}
193+
194+
_ = Some(T(114)).as_mut().inspect(|t| {
195+
t.0 + 514;
196+
});
197+
198+
_ = Some(T(114)).as_mut().map(|t| {
199+
t.0 = 514;
200+
t
201+
});
202+
203+
_ = Some(T(114)).as_mut().map(|t| {
204+
t.0 += 514;
205+
t
206+
});
207+
208+
// FIXME: It's better to lint this case
209+
_ = Some(T(114)).as_mut().map(|t| {
210+
let indirect = t;
211+
indirect.0 + 514;
212+
indirect
213+
});
214+
215+
_ = Some(T(114)).as_mut().map(|t| {
216+
let indirect = t;
217+
indirect.0 += 514;
218+
indirect
219+
});
220+
221+
_ = Some(T(114)).as_mut().map(|t| {
222+
t.do_mut();
223+
t
224+
});
225+
226+
_ = Some(T(114)).as_mut().inspect(|t| {
227+
t.do_immut();
228+
});
229+
230+
_ = Some(T(114)).as_mut().map(|t| {
231+
T::do_mut(t);
232+
t
233+
});
234+
235+
_ = Some(T(114)).as_mut().inspect(|t| {
236+
T::do_immut(t);
237+
});
238+
239+
// FIXME: It's better to lint this case
240+
_ = Some(T(114)).as_mut().map(|t| {
241+
let indirect = t;
242+
indirect.do_immut();
243+
indirect
244+
});
245+
246+
// FIXME: It's better to lint this case
247+
_ = Some(T(114)).as_mut().map(|t| {
248+
(&*t).do_immut();
249+
t
250+
});
251+
252+
// Nested fields access
253+
struct N(T);
254+
255+
_ = Some(N(T(114))).as_mut().map(|t| {
256+
t.0.do_mut();
257+
t
258+
});
259+
260+
_ = Some(N(T(114))).as_mut().inspect(|t| {
261+
t.0.do_immut();
262+
});
263+
264+
_ = Some(N(T(114))).as_mut().map(|t| {
265+
T::do_mut(&mut t.0);
266+
t
267+
});
268+
269+
_ = Some(N(T(114))).as_mut().inspect(|t| {
270+
T::do_immut(&t.0);
271+
});
272+
}

0 commit comments

Comments
 (0)