Skip to content

Commit 640be36

Browse files
committed
Jarcho's suggestion
1 parent e95dbbf commit 640be36

File tree

3 files changed

+132
-46
lines changed

3 files changed

+132
-46
lines changed
Lines changed: 54 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,66 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::expr_custom_deref_adjustment;
32
use clippy_utils::res::MaybeDef;
4-
use clippy_utils::ty::peel_and_count_ty_refs;
3+
use clippy_utils::ty::implements_trait;
54
use rustc_errors::Applicability;
6-
use rustc_hir::{Expr, ExprKind, Mutability};
5+
use rustc_hir::{Expr, ExprKind, Mutability, UnOp};
76
use rustc_lint::LateContext;
7+
use rustc_middle::ty::adjustment::Adjust;
88
use rustc_span::{Span, sym};
99

1010
use super::MUT_MUTEX_LOCK;
1111

1212
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, name_span: Span) {
13-
// given a `recv` like `a.b.mutex`, this returns `[a.b.mutex, a.b, a]`
14-
let mut projection_chain = std::iter::successors(Some(recv), |recv| {
15-
if let ExprKind::Index(r, ..) | ExprKind::Field(r, _) = recv.kind {
16-
Some(r)
17-
} else {
18-
None
19-
}
20-
});
13+
let typeck = cx.typeck_results();
14+
if !typeck.expr_ty_adjusted(recv).peel_refs().is_diag_item(cx, sym::Mutex) {
15+
return;
16+
}
2117

22-
if (cx.typeck_results().expr_ty_adjusted(recv))
23-
.peel_refs()
24-
.is_diag_item(cx, sym::Mutex)
25-
// If, somewhere along the projection chain, we stumble upon a field of type `&T`, or dereference a
26-
// type like `Arc<T>` to `&T`, we no longer have mutable access to the undelying `Mutex`
27-
&& projection_chain.all(|recv| {
28-
let expr_ty = cx.typeck_results().expr_ty(recv);
29-
// The reason we don't use `expr_ty_adjusted` here is twofold:
30-
//
31-
// Consider code like this:
32-
// ```rs
33-
// struct Foo(Mutex<i32>);
34-
//
35-
// fn fun(f: &Foo) {
36-
// f.0.lock()
37-
// }
38-
// ```
39-
// - In the outermost receiver (`f.0`), the adjusted type would be `&Mutex`, due to an adjustment
40-
// performed by `Mutex::lock`.
41-
// - In the intermediary receivers (here, only `f`), the adjusted type would be fully dereferenced
42-
// (`Foo`), which would make us miss the fact that `f` is actually behind a `&` -- this
43-
// information is preserved in the pre-adjustment type (`&Foo`)
44-
peel_and_count_ty_refs(expr_ty).2 != Some(Mutability::Not)
45-
&& expr_custom_deref_adjustment(cx, recv) != Some(Mutability::Not)
46-
})
47-
{
48-
span_lint_and_sugg(
49-
cx,
50-
MUT_MUTEX_LOCK,
51-
name_span,
52-
"calling `&mut Mutex::lock` unnecessarily locks an exclusive (mutable) reference",
53-
"change this to",
54-
"get_mut".to_owned(),
55-
Applicability::MaybeIncorrect,
56-
);
18+
let deref_mut_trait = cx.tcx.lang_items().deref_mut_trait();
19+
let index_mut_trait = cx.tcx.lang_items().index_mut_trait();
20+
let impls_deref_mut = |ty| deref_mut_trait.is_some_and(|trait_id| implements_trait(cx, ty, trait_id, &[]));
21+
let impls_index_mut = |ty, idx| index_mut_trait.is_some_and(|trait_id| implements_trait(cx, ty, trait_id, &[idx]));
22+
let mut r = recv;
23+
'outer: loop {
24+
if !(typeck.expr_adjustments(r))
25+
.iter()
26+
.map_while(|a| match a.kind {
27+
Adjust::Deref(x) => Some((a.target, x)),
28+
_ => None,
29+
})
30+
.try_fold(typeck.expr_ty(r), |ty, (target, deref)| match deref {
31+
None => (ty.ref_mutability() != Some(Mutability::Not)).then_some(target),
32+
Some(_) => impls_deref_mut(ty).then_some(target),
33+
})
34+
.is_some()
35+
{
36+
return;
37+
}
38+
loop {
39+
match r.kind {
40+
ExprKind::Field(base, _) => r = base,
41+
ExprKind::Index(base, idx, _)
42+
if impls_index_mut(typeck.expr_ty_adjusted(base), typeck.expr_ty_adjusted(idx).into()) =>
43+
{
44+
r = base;
45+
},
46+
// `base` here can't actually have adjustments
47+
ExprKind::Unary(UnOp::Deref, base) if impls_deref_mut(typeck.expr_ty_adjusted(base)) => {
48+
r = base;
49+
continue;
50+
},
51+
_ => break 'outer,
52+
}
53+
continue 'outer;
54+
}
5755
}
56+
57+
span_lint_and_sugg(
58+
cx,
59+
MUT_MUTEX_LOCK,
60+
name_span,
61+
"calling `&mut Mutex::lock` unnecessarily locks an exclusive (mutable) reference",
62+
"change this to",
63+
"get_mut".to_owned(),
64+
Applicability::MaybeIncorrect,
65+
);
5866
}

tests/ui/mut_mutex_lock.fixed

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,45 @@ mod issue16253 {
278278
let exes: &_ = mutexes;
279279
exes[0].lock().unwrap();
280280
}
281+
282+
mod deref_but_not_deref_mut {
283+
use super::*;
284+
285+
struct Foo(Mutex<i32>);
286+
287+
impl std::ops::Deref for Foo {
288+
type Target = Mutex<i32>;
289+
290+
fn deref(&self) -> &Self::Target {
291+
&self.0
292+
}
293+
}
294+
295+
fn main(f: &mut Foo) {
296+
// This can't be changed to use `get_mut`, as `Foo: DerefMut<Target = Mutex<i32>>` doesn't hold
297+
f.lock().unwrap();
298+
}
299+
}
300+
301+
mod index_but_not_index_mut {
302+
use super::*;
303+
304+
struct Foo(Mutex<i32>);
305+
306+
impl std::ops::Index<usize> for Foo {
307+
type Output = Mutex<i32>;
308+
309+
fn index(&self, _: usize) -> &Self::Output {
310+
&self.0
311+
}
312+
}
313+
314+
fn main(f: &mut Foo) {
315+
// This can't be changed to use `index_mut`, as `Foo: IndexMut<usize, Output = Mutex<i32>>` doesn't
316+
// hold
317+
f[0].lock().unwrap();
318+
}
319+
}
281320
}
282321

283322
fn main() {}

tests/ui/mut_mutex_lock.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,45 @@ mod issue16253 {
278278
let exes: &_ = mutexes;
279279
exes[0].lock().unwrap();
280280
}
281+
282+
mod deref_but_not_deref_mut {
283+
use super::*;
284+
285+
struct Foo(Mutex<i32>);
286+
287+
impl std::ops::Deref for Foo {
288+
type Target = Mutex<i32>;
289+
290+
fn deref(&self) -> &Self::Target {
291+
&self.0
292+
}
293+
}
294+
295+
fn main(f: &mut Foo) {
296+
// This can't be changed to use `get_mut`, as `Foo: DerefMut<Target = Mutex<i32>>` doesn't hold
297+
f.lock().unwrap();
298+
}
299+
}
300+
301+
mod index_but_not_index_mut {
302+
use super::*;
303+
304+
struct Foo(Mutex<i32>);
305+
306+
impl std::ops::Index<usize> for Foo {
307+
type Output = Mutex<i32>;
308+
309+
fn index(&self, _: usize) -> &Self::Output {
310+
&self.0
311+
}
312+
}
313+
314+
fn main(f: &mut Foo) {
315+
// This can't be changed to use `index_mut`, as `Foo: IndexMut<usize, Output = Mutex<i32>>` doesn't
316+
// hold
317+
f[0].lock().unwrap();
318+
}
319+
}
281320
}
282321

283322
fn main() {}

0 commit comments

Comments
 (0)