@@ -5,7 +5,7 @@ use crate::{
5
5
use rustc_errors:: MultiSpan ;
6
6
use rustc_hir as hir;
7
7
use rustc_middle:: ty;
8
- use rustc_span:: Symbol ;
8
+ use rustc_span:: { sym , Symbol } ;
9
9
10
10
declare_lint ! {
11
11
/// The `let_underscore_drop` lint checks for statements which don't bind
@@ -105,46 +105,65 @@ const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [
105
105
106
106
impl < ' tcx > LateLintPass < ' tcx > for LetUnderscore {
107
107
fn check_local ( & mut self , cx : & LateContext < ' _ > , local : & hir:: Local < ' _ > ) {
108
- if !matches ! ( local. pat. kind, hir:: PatKind :: Wild ) {
109
- return ;
110
- }
111
- if let Some ( init) = local. init {
112
- let init_ty = cx. typeck_results ( ) . expr_ty ( init) ;
108
+ let mut top_level = true ;
109
+
110
+ // We recursively walk through all patterns, so that we can catch cases where the lock is nested in a pattern.
111
+ // For the basic `let_underscore_drop` lint, we only look at the top level, since there are many legitimate reasons
112
+ // to bind a sub-pattern to an `_`, if we're only interested in the rest.
113
+ // But with locks, we prefer having the chance of "false positives" over missing cases, since the effects can be
114
+ // quite catastrophic.
115
+ local. pat . walk_always ( |pat| {
116
+ let is_top_level = top_level;
117
+ top_level = false ;
118
+
119
+ if !matches ! ( pat. kind, hir:: PatKind :: Wild ) {
120
+ return ;
121
+ }
122
+
123
+ let ty = cx. typeck_results ( ) . pat_ty ( pat) ;
124
+
113
125
// If the type has a trivial Drop implementation, then it doesn't
114
126
// matter that we drop the value immediately.
115
- if !init_ty . needs_drop ( cx. tcx , cx. param_env ) {
127
+ if !ty . needs_drop ( cx. tcx , cx. param_env ) {
116
128
return ;
117
129
}
118
- let is_sync_lock = match init_ty. kind ( ) {
130
+ // Lint for patterns like `mutex.lock()`, which returns `Result<MutexGuard, _>` as well.
131
+ let potential_lock_type = match ty. kind ( ) {
132
+ ty:: Adt ( adt, args) if cx. tcx . is_diagnostic_item ( sym:: Result , adt. did ( ) ) => {
133
+ args. type_at ( 0 )
134
+ }
135
+ _ => ty,
136
+ } ;
137
+ let is_sync_lock = match potential_lock_type. kind ( ) {
119
138
ty:: Adt ( adt, _) => SYNC_GUARD_SYMBOLS
120
139
. iter ( )
121
140
. any ( |guard_symbol| cx. tcx . is_diagnostic_item ( * guard_symbol, adt. did ( ) ) ) ,
122
141
_ => false ,
123
142
} ;
124
143
144
+ let can_use_init = is_top_level. then_some ( local. init ) . flatten ( ) ;
145
+
125
146
let sub = NonBindingLetSub {
126
- suggestion : local. pat . span ,
127
- multi_suggestion_start : local. span . until ( init. span ) ,
128
- multi_suggestion_end : init. span . shrink_to_hi ( ) ,
147
+ suggestion : pat. span ,
148
+ // We can't suggest `drop()` when we're on the top level.
149
+ drop_fn_start_end : can_use_init
150
+ . map ( |init| ( local. span . until ( init. span ) , init. span . shrink_to_hi ( ) ) ) ,
129
151
} ;
130
152
if is_sync_lock {
131
- let mut span = MultiSpan :: from_spans ( vec ! [ local . pat. span, init . span ] ) ;
153
+ let mut span = MultiSpan :: from_span ( pat. span ) ;
132
154
span. push_span_label (
133
- local . pat . span ,
155
+ pat. span ,
134
156
"this lock is not assigned to a binding and is immediately dropped" . to_string ( ) ,
135
157
) ;
136
- span. push_span_label (
137
- init. span ,
138
- "this binding will immediately drop the value assigned to it" . to_string ( ) ,
139
- ) ;
140
158
cx. emit_spanned_lint ( LET_UNDERSCORE_LOCK , span, NonBindingLet :: SyncLock { sub } ) ;
141
- } else {
159
+ // Only emit let_underscore_drop for top-level `_` patterns.
160
+ } else if can_use_init. is_some ( ) {
142
161
cx. emit_spanned_lint (
143
162
LET_UNDERSCORE_DROP ,
144
163
local. span ,
145
164
NonBindingLet :: DropType { sub } ,
146
165
) ;
147
166
}
148
- }
167
+ } ) ;
149
168
}
150
169
}
0 commit comments