|
1 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
2 | | -use clippy_utils::expr_custom_deref_adjustment; |
3 | 2 | use clippy_utils::res::MaybeDef; |
4 | | -use clippy_utils::ty::peel_and_count_ty_refs; |
| 3 | +use clippy_utils::ty::implements_trait; |
5 | 4 | use rustc_errors::Applicability; |
6 | | -use rustc_hir::{Expr, ExprKind, Mutability}; |
| 5 | +use rustc_hir::{Expr, ExprKind, Mutability, UnOp}; |
7 | 6 | use rustc_lint::LateContext; |
| 7 | +use rustc_middle::ty::adjustment::Adjust; |
8 | 8 | use rustc_span::{Span, sym}; |
9 | 9 |
|
10 | 10 | use super::MUT_MUTEX_LOCK; |
11 | 11 |
|
12 | 12 | 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 | + } |
21 | 17 |
|
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 | + } |
57 | 55 | } |
| 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 | + ); |
58 | 66 | } |
0 commit comments