Skip to content

Commit f244b83

Browse files
committed
extract projection checking into a function
makes the structure of the lint a bit clearer imo
1 parent b28db0a commit f244b83

1 file changed

Lines changed: 65 additions & 60 deletions

File tree

clippy_lints/src/methods/mut_mutex_lock.rs

Lines changed: 65 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ use rustc_span::{Span, sym};
1111
use super::MUT_MUTEX_LOCK;
1212

1313
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, name_span: Span) {
14-
let typeck = cx.typeck_results();
15-
1614
// Make sure that we have a mutable access to `Mutex`:
1715
// 1. Check that the receiver is behind one or more `&mut`s -- we want to have mutable access, while
1816
// not outright owning it -- if the latter were the case, then changing `.lock()` to
@@ -22,70 +20,21 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, name_s
2220
// references, so no need to check `n`
2321
// NOTE: the reason we don't use `expr_ty_adjusted` here is that a call
2422
// to `Mutex::lock` by itself adjusts the receiver to be `&Mutex`
25-
if let (recv_ty, _, Some(Mutability::Mut)) = peel_and_count_ty_refs(typeck.expr_ty(recv))
23+
if let (recv_ty, _, Some(Mutability::Mut)) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(recv))
2624
&& recv_ty.is_diag_item(cx, sym::Mutex)
27-
{
28-
let deref_mut_trait = cx.tcx.lang_items().deref_mut_trait();
29-
let impls_deref_mut = |ty| deref_mut_trait.is_some_and(|trait_id| implements_trait(cx, ty, trait_id, &[]));
30-
3125
// 2. The mutex was accessed either directly (`mutex.lock()`), or through a series of
3226
// deref/field/indexing projections. Since the final `.lock()` call only requires `&Mutex`,
3327
// those might be immutable, and so we need to manually check whether mutable projections
3428
// would've been possible. For that, we'll repeatedly peel off projections and check each
3529
// intermediary receiver.
36-
let mut r = recv;
37-
loop {
38-
// Check that the (deref) adjustments could be made mutable
39-
if (typeck.expr_adjustments(r))
40-
.iter()
41-
.map_while(|a| match a.kind {
42-
Adjust::Deref(x) => Some((a.target, x)),
43-
_ => None,
44-
})
45-
.try_fold(typeck.expr_ty(r), |ty, (target, deref)| match deref {
46-
// There has been an overloaded deref, most likely an immutable one, as `.lock()` didn't require a
47-
// mutable one -- we need to check if a mutable deref would've been possible, i.e. if
48-
// `ty: DerefMut<Target = target>` (we don't need to check the `Target` part, as `Deref` and
49-
// `DerefMut` impls necessarily have the same one)
50-
DerefAdjustKind::Overloaded(_) => impls_deref_mut(ty).then_some(target),
51-
// There has been a simple deref; if it happened on a `&T`, then we know it will can't be changed to
52-
// provide mutable access
53-
DerefAdjustKind::Builtin => (ty.ref_mutability() != Some(Mutability::Not)).then_some(target),
54-
DerefAdjustKind::Pin => Some(target),
55-
})
56-
.is_none()
57-
{
58-
return;
59-
}
60-
61-
// Peel off one projection
62-
match r.kind {
63-
ExprKind::Unary(UnOp::Deref, base) => {
64-
if impls_deref_mut(typeck.expr_ty_adjusted(base)) {
65-
r = base;
66-
} else {
67-
return;
68-
}
69-
},
70-
ExprKind::Index(..) | ExprKind::Field(..) => {
71-
// We don't want to lint on indexing and field accesses, as both of those would take exclusive
72-
// access to only part of a value -- which would conflict with any immutable reborrow over
73-
// the whole value
74-
return;
75-
},
76-
_ => {
77-
// We arrived at the innermost receiver
78-
if let ExprKind::Path(ref p) = r.kind
79-
&& let Some(did) = cx.qpath_res(p, r.hir_id).opt_def_id()
80-
&& matches!(cx.tcx.def_kind(did), DefKind::Static { .. })
81-
{
82-
// Don't lint on statics
83-
return;
84-
}
85-
// No more projections to check
86-
break;
87-
},
88-
}
30+
&& let Some(recv) = check_projections(cx, recv)
31+
{
32+
if let ExprKind::Path(ref p) = recv.kind
33+
&& let Some(did) = cx.qpath_res(p, recv.hir_id).opt_def_id()
34+
&& matches!(cx.tcx.def_kind(did), DefKind::Static { .. })
35+
{
36+
// Don't lint on statics
37+
return;
8938
}
9039

9140
span_lint_and_sugg(
@@ -99,3 +48,59 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, name_s
9948
);
10049
}
10150
}
51+
52+
/// See the comment at the callsite.
53+
///
54+
/// Returns the innermost receiver if all the projections could be made mutable.
55+
fn check_projections<'tcx>(cx: &LateContext<'tcx>, mut recv: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
56+
let deref_mut_trait = cx.tcx.lang_items().deref_mut_trait();
57+
let impls_deref_mut = |ty| deref_mut_trait.is_some_and(|trait_id| implements_trait(cx, ty, trait_id, &[]));
58+
let typeck = cx.typeck_results();
59+
60+
loop {
61+
// Check that the (deref) adjustments could be made mutable
62+
if (typeck.expr_adjustments(recv))
63+
.iter()
64+
.map_while(|a| match a.kind {
65+
Adjust::Deref(x) => Some((a.target, x)),
66+
_ => None,
67+
})
68+
.try_fold(typeck.expr_ty(recv), |ty, (target, deref)| match deref {
69+
// There has been an overloaded deref, most likely an immutable one, as `.lock()` didn't require a
70+
// mutable one -- we need to check if a mutable deref would've been possible, i.e. if
71+
// `ty: DerefMut<Target = target>` (we don't need to check the `Target` part, as `Deref` and
72+
// `DerefMut` impls necessarily have the same one)
73+
DerefAdjustKind::Overloaded(_) => impls_deref_mut(ty).then_some(target),
74+
// There has been a simple deref; if it happened on a `&T`, then we know it will can't be changed to
75+
// provide mutable access
76+
DerefAdjustKind::Builtin => (ty.ref_mutability() != Some(Mutability::Not)).then_some(target),
77+
DerefAdjustKind::Pin => Some(target),
78+
})
79+
.is_none()
80+
{
81+
return None;
82+
}
83+
84+
// Peel off one projection
85+
match recv.kind {
86+
ExprKind::Unary(UnOp::Deref, base) => {
87+
if impls_deref_mut(typeck.expr_ty_adjusted(base)) {
88+
recv = base;
89+
} else {
90+
return None;
91+
}
92+
},
93+
ExprKind::Index(..) | ExprKind::Field(..) => {
94+
// We don't want to lint on indexing and field accesses, as both of those would take exclusive
95+
// access to only part of a value -- which would conflict with any immutable reborrow over
96+
// the whole value
97+
return None;
98+
},
99+
_ => {
100+
// No more projections to check
101+
break;
102+
},
103+
}
104+
}
105+
Some(recv)
106+
}

0 commit comments

Comments
 (0)