@@ -11,8 +11,6 @@ use rustc_span::{Span, sym};
1111use super :: MUT_MUTEX_LOCK ;
1212
1313pub ( 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