Skip to content

Commit d45f4ef

Browse files
committed
Fix manual_inspect to consider mutability
1 parent 3caff99 commit d45f4ef

File tree

6 files changed

+339
-33
lines changed

6 files changed

+339
-33
lines changed

clippy_lints/src/dereference.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
269269
RefOp::Deref if use_cx.same_ctxt => {
270270
let use_node = use_cx.use_node(cx);
271271
let sub_ty = typeck.expr_ty(sub_expr);
272-
if let ExprUseNode::FieldAccess(name) = use_node
272+
if let ExprUseNode::FieldAccess(_, name) = use_node
273273
&& !use_cx.moved_before_use
274274
&& !ty_contains_field(sub_ty, name.name)
275275
{
@@ -338,7 +338,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
338338
TyCoercionStability::for_defined_ty(cx, ty, use_node.is_return())
339339
});
340340
let can_auto_borrow = match use_node {
341-
ExprUseNode::FieldAccess(_)
341+
ExprUseNode::FieldAccess(_, _)
342342
if !use_cx.moved_before_use && matches!(sub_expr.kind, ExprKind::Field(..)) =>
343343
{
344344
// `DerefMut` will not be automatically applied to `ManuallyDrop<_>`
@@ -349,7 +349,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
349349
// deref through `ManuallyDrop<_>` will not compile.
350350
!adjust_derefs_manually_drop(use_cx.adjustments, expr_ty)
351351
},
352-
ExprUseNode::Callee | ExprUseNode::FieldAccess(_) if !use_cx.moved_before_use => true,
352+
ExprUseNode::Callee | ExprUseNode::FieldAccess(_, _) if !use_cx.moved_before_use => true,
353353
ExprUseNode::MethodArg(hir_id, _, 0) if !use_cx.moved_before_use => {
354354
// Check for calls to trait methods where the trait is implemented
355355
// on a reference.
@@ -437,7 +437,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
437437
count: deref_count - required_refs,
438438
msg,
439439
stability,
440-
for_field_access: if let ExprUseNode::FieldAccess(name) = use_node
440+
for_field_access: if let ExprUseNode::FieldAccess(_, name) = use_node
441441
&& !use_cx.moved_before_use
442442
{
443443
Some(name.name)

clippy_lints/src/methods/manual_inspect.rs

Lines changed: 14 additions & 9 deletions
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 || arg_ty.is_copy_modulo_regions(cx.tcx, cx.param_env))
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.param_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

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ use std::sync::{Mutex, MutexGuard, OnceLock};
9191

9292
use clippy_config::types::DisallowedPath;
9393
use itertools::Itertools;
94-
use rustc_ast::ast::{self, LitKind, RangeLimits};
94+
use rustc_ast::ast::{self, BinOpKind, LitKind, RangeLimits};
9595
use rustc_data_structures::fx::FxHashMap;
9696
use rustc_data_structures::packed::Pu128;
9797
use rustc_data_structures::unhash::UnhashMap;
@@ -2791,16 +2791,46 @@ impl<'tcx> ExprUseCtxt<'tcx> {
27912791
.position(|arg| arg.hir_id == self.child_id)
27922792
.map_or(0, |i| i + 1),
27932793
),
2794-
ExprKind::Field(_, name) => ExprUseNode::FieldAccess(name),
2794+
ExprKind::Field(_, name) => ExprUseNode::FieldAccess(use_expr.hir_id, name),
27952795
ExprKind::AddrOf(kind, mutbl, _) => ExprUseNode::AddrOf(kind, mutbl),
2796+
ExprKind::Assign(lhs, rhs, _) => {
2797+
debug_assert!(lhs.hir_id == self.child_id || rhs.hir_id == self.child_id);
2798+
#[allow(clippy::bool_to_int_with_if)]
2799+
let idx = if lhs.hir_id == self.child_id { 0 } else { 1 };
2800+
ExprUseNode::Assign(idx)
2801+
},
2802+
ExprKind::AssignOp(op, lhs, rhs) => {
2803+
debug_assert!(lhs.hir_id == self.child_id || rhs.hir_id == self.child_id);
2804+
#[allow(clippy::bool_to_int_with_if)]
2805+
let idx = if lhs.hir_id == self.child_id { 0 } else { 1 };
2806+
ExprUseNode::AssignOp(op.node, idx)
2807+
},
27962808
_ => ExprUseNode::Other,
27972809
},
27982810
_ => ExprUseNode::Other,
27992811
}
28002812
}
2813+
2814+
pub fn use_mutability(&self, cx: &LateContext<'tcx>) -> Mutability {
2815+
match self.use_node(cx) {
2816+
ExprUseNode::FieldAccess(parent, _) => {
2817+
let parent = cx.tcx.hir().expect_expr(parent);
2818+
expr_use_ctxt(cx, parent).use_mutability(cx)
2819+
},
2820+
ExprUseNode::AssignOp(_, 0) | ExprUseNode::Assign(0) => Mutability::Mut,
2821+
ExprUseNode::AddrOf(_, mutbl) => mutbl,
2822+
ExprUseNode::FnArg(_, _) | ExprUseNode::MethodArg(_, _, _) => {
2823+
let child_expr = cx.tcx.hir().expect_expr(self.child_id);
2824+
let ty = cx.typeck_results().expr_ty_adjusted(child_expr);
2825+
ty.ref_mutability().unwrap_or(Mutability::Not)
2826+
},
2827+
_ => Mutability::Not,
2828+
}
2829+
}
28012830
}
28022831

28032832
/// The node which consumes a value.
2833+
#[derive(Debug)]
28042834
pub enum ExprUseNode<'tcx> {
28052835
/// Assignment to, or initializer for, a local
28062836
LetStmt(&'tcx LetStmt<'tcx>),
@@ -2817,9 +2847,13 @@ pub enum ExprUseNode<'tcx> {
28172847
/// The callee of a function call.
28182848
Callee,
28192849
/// Access of a field.
2820-
FieldAccess(Ident),
2850+
FieldAccess(HirId, Ident),
28212851
/// Borrow expression.
28222852
AddrOf(ast::BorrowKind, Mutability),
2853+
/// Assignment.
2854+
Assign(usize),
2855+
/// Assignment with an operator.
2856+
AssignOp(BinOpKind, usize),
28232857
Other,
28242858
}
28252859
impl<'tcx> ExprUseNode<'tcx> {
@@ -2896,7 +2930,13 @@ impl<'tcx> ExprUseNode<'tcx> {
28962930
let sig = cx.tcx.fn_sig(id).skip_binder();
28972931
Some(DefinedTy::Mir(cx.tcx.param_env(id).and(sig.input(i))))
28982932
},
2899-
Self::LetStmt(_) | Self::FieldAccess(..) | Self::Callee | Self::Other | Self::AddrOf(..) => None,
2933+
Self::LetStmt(_)
2934+
| Self::FieldAccess(..)
2935+
| Self::Callee
2936+
| Self::Other
2937+
| Self::AddrOf(..)
2938+
| Self::Assign(_)
2939+
| Self::AssignOp(..) => None,
29002940
}
29012941
}
29022942
}

tests/ui/manual_inspect.fixed

Lines changed: 99 additions & 1 deletion
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)