@@ -5,7 +5,7 @@ use crate::{
55use rustc_errors:: MultiSpan ;
66use rustc_hir as hir;
77use rustc_middle:: ty;
8- use rustc_span:: Symbol ;
8+ use rustc_span:: { sym , Symbol } ;
99
1010declare_lint ! {
1111 /// The `let_underscore_drop` lint checks for statements which don't bind
@@ -105,51 +105,70 @@ const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [
105105
106106impl < ' tcx > LateLintPass < ' tcx > for LetUnderscore {
107107 fn check_local ( & mut self , cx : & LateContext < ' _ > , local : & hir:: Local < ' _ > ) {
108- if !matches ! ( local. pat. kind, hir:: PatKind :: Wild ) {
109- return ;
110- }
111-
112108 if matches ! ( local. source, rustc_hir:: LocalSource :: AsyncFn ) {
113109 return ;
114110 }
115- if let Some ( init) = local. init {
116- let init_ty = cx. typeck_results ( ) . expr_ty ( init) ;
111+
112+ let mut top_level = true ;
113+
114+ // We recursively walk through all patterns, so that we can catch cases where the lock is nested in a pattern.
115+ // For the basic `let_underscore_drop` lint, we only look at the top level, since there are many legitimate reasons
116+ // to bind a sub-pattern to an `_`, if we're only interested in the rest.
117+ // But with locks, we prefer having the chance of "false positives" over missing cases, since the effects can be
118+ // quite catastrophic.
119+ local. pat . walk_always ( |pat| {
120+ let is_top_level = top_level;
121+ top_level = false ;
122+
123+ if !matches ! ( pat. kind, hir:: PatKind :: Wild ) {
124+ return ;
125+ }
126+
127+ let ty = cx. typeck_results ( ) . pat_ty ( pat) ;
128+
117129 // If the type has a trivial Drop implementation, then it doesn't
118130 // matter that we drop the value immediately.
119- if !init_ty . needs_drop ( cx. tcx , cx. param_env ) {
131+ if !ty . needs_drop ( cx. tcx , cx. param_env ) {
120132 return ;
121133 }
122- let is_sync_lock = match init_ty. kind ( ) {
134+ // Lint for patterns like `mutex.lock()`, which returns `Result<MutexGuard, _>` as well.
135+ let potential_lock_type = match ty. kind ( ) {
136+ ty:: Adt ( adt, args) if cx. tcx . is_diagnostic_item ( sym:: Result , adt. did ( ) ) => {
137+ args. type_at ( 0 )
138+ }
139+ _ => ty,
140+ } ;
141+ let is_sync_lock = match potential_lock_type. kind ( ) {
123142 ty:: Adt ( adt, _) => SYNC_GUARD_SYMBOLS
124143 . iter ( )
125144 . any ( |guard_symbol| cx. tcx . is_diagnostic_item ( * guard_symbol, adt. did ( ) ) ) ,
126145 _ => false ,
127146 } ;
128147
148+ let can_use_init = is_top_level. then_some ( local. init ) . flatten ( ) ;
149+
129150 let sub = NonBindingLetSub {
130- suggestion : local. pat . span ,
131- multi_suggestion_start : local. span . until ( init. span ) ,
132- multi_suggestion_end : init. span . shrink_to_hi ( ) ,
151+ suggestion : pat. span ,
152+ // We can't suggest `drop()` when we're on the top level.
153+ drop_fn_start_end : can_use_init
154+ . map ( |init| ( local. span . until ( init. span ) , init. span . shrink_to_hi ( ) ) ) ,
133155 is_assign_desugar : matches ! ( local. source, rustc_hir:: LocalSource :: AssignDesugar ( _) ) ,
134156 } ;
135157 if is_sync_lock {
136- let mut span = MultiSpan :: from_spans ( vec ! [ local . pat. span, init . span ] ) ;
158+ let mut span = MultiSpan :: from_span ( pat. span ) ;
137159 span. push_span_label (
138- local . pat . span ,
160+ pat. span ,
139161 "this lock is not assigned to a binding and is immediately dropped" . to_string ( ) ,
140162 ) ;
141- span. push_span_label (
142- init. span ,
143- "this binding will immediately drop the value assigned to it" . to_string ( ) ,
144- ) ;
145163 cx. emit_spanned_lint ( LET_UNDERSCORE_LOCK , span, NonBindingLet :: SyncLock { sub } ) ;
146- } else {
164+ // Only emit let_underscore_drop for top-level `_` patterns.
165+ } else if can_use_init. is_some ( ) {
147166 cx. emit_spanned_lint (
148167 LET_UNDERSCORE_DROP ,
149168 local. span ,
150169 NonBindingLet :: DropType { sub } ,
151170 ) ;
152171 }
153- }
172+ } ) ;
154173 }
155174}
0 commit comments