@@ -158,7 +158,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
158158 self . check_patterns ( pat, Refutable ) ;
159159 let mut cx = self . new_cx ( scrutinee. hir_id ) ;
160160 let tpat = self . lower_pattern ( & mut cx, pat, & mut false ) ;
161- check_let_reachability ( & mut cx, pat. hir_id , tpat, span) ;
161+ self . check_let_reachability ( & mut cx, pat. hir_id , tpat, span) ;
162162 }
163163
164164 fn check_match (
@@ -176,7 +176,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
176176 if let Some ( hir:: Guard :: IfLet ( ref pat, _) ) = arm. guard {
177177 self . check_patterns ( pat, Refutable ) ;
178178 let tpat = self . lower_pattern ( & mut cx, pat, & mut false ) ;
179- check_let_reachability ( & mut cx, pat. hir_id , tpat, tpat. span ( ) ) ;
179+ self . check_let_reachability ( & mut cx, pat. hir_id , tpat, tpat. span ( ) ) ;
180180 }
181181 }
182182
@@ -224,6 +224,157 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
224224 }
225225 }
226226
227+ fn check_let_reachability (
228+ & mut self ,
229+ cx : & mut MatchCheckCtxt < ' p , ' tcx > ,
230+ pat_id : HirId ,
231+ pat : & ' p DeconstructedPat < ' p , ' tcx > ,
232+ span : Span ,
233+ ) {
234+ if self . check_let_chain ( cx, pat_id) {
235+ return ;
236+ }
237+
238+ if is_let_irrefutable ( cx, pat_id, pat) {
239+ irrefutable_let_pattern ( cx. tcx , pat_id, span) ;
240+ }
241+ }
242+
243+ fn check_let_chain ( & mut self , cx : & mut MatchCheckCtxt < ' p , ' tcx > , pat_id : HirId ) -> bool {
244+ let hir = self . tcx . hir ( ) ;
245+ let parent = hir. get_parent_node ( pat_id) ;
246+
247+ // First, figure out if the given pattern is part of a let chain,
248+ // and if so, obtain the top node of the chain.
249+ let mut top = parent;
250+ let mut part_of_chain = false ;
251+ loop {
252+ let new_top = hir. get_parent_node ( top) ;
253+ if let hir:: Node :: Expr (
254+ hir:: Expr {
255+ kind : hir:: ExprKind :: Binary ( Spanned { node : hir:: BinOpKind :: And , .. } , lhs, rhs) ,
256+ ..
257+ } ,
258+ ..,
259+ ) = hir. get ( new_top)
260+ {
261+ // If this isn't the first iteration, we need to check
262+ // if there is a let expr before us in the chain, so
263+ // that we avoid doubly checking the let chain.
264+
265+ // The way a chain of &&s is encoded is ((let ... && let ...) && let ...) && let ...
266+ // as && is left-to-right associative. Thus, we need to check rhs.
267+ if part_of_chain && matches ! ( rhs. kind, hir:: ExprKind :: Let ( ..) ) {
268+ return true ;
269+ }
270+ // If there is a let at the lhs, and we provide the rhs, we don't do any checking either.
271+ if !part_of_chain && matches ! ( lhs. kind, hir:: ExprKind :: Let ( ..) ) && rhs. hir_id == top
272+ {
273+ return true ;
274+ }
275+ } else {
276+ // We've reached the top.
277+ break ;
278+ }
279+
280+ // Since this function is called within a let context, it is reasonable to assume that any parent
281+ // `&&` infers a let chain
282+ part_of_chain = true ;
283+ top = new_top;
284+ }
285+ if !part_of_chain {
286+ return false ;
287+ }
288+
289+ // Second, obtain the refutabilities of all exprs in the chain,
290+ // and record chain members that aren't let exprs.
291+ let mut chain_refutabilities = Vec :: new ( ) ;
292+ let hir:: Node :: Expr ( top_expr) = hir. get ( top) else {
293+ // We ensure right above that it's an Expr
294+ unreachable ! ( )
295+ } ;
296+ let mut cur_expr = top_expr;
297+ loop {
298+ let mut add = |expr : & hir:: Expr < ' tcx > | {
299+ let refutability = match expr. kind {
300+ hir:: ExprKind :: Let ( hir:: Let { pat, init, span, .. } ) => {
301+ let mut ncx = self . new_cx ( init. hir_id ) ;
302+ let tpat = self . lower_pattern ( & mut ncx, pat, & mut false ) ;
303+
304+ let refutable = !is_let_irrefutable ( & mut ncx, pat. hir_id , tpat) ;
305+ Some ( ( * span, refutable) )
306+ }
307+ _ => None ,
308+ } ;
309+ chain_refutabilities. push ( refutability) ;
310+ } ;
311+ if let hir:: Expr {
312+ kind : hir:: ExprKind :: Binary ( Spanned { node : hir:: BinOpKind :: And , .. } , lhs, rhs) ,
313+ ..
314+ } = cur_expr
315+ {
316+ add ( rhs) ;
317+ cur_expr = lhs;
318+ } else {
319+ add ( cur_expr) ;
320+ break ;
321+ }
322+ }
323+ chain_refutabilities. reverse ( ) ;
324+
325+ // Third, emit the actual warnings.
326+
327+ if chain_refutabilities. iter ( ) . all ( |r| matches ! ( * r, Some ( ( _, false ) ) ) ) {
328+ // The entire chain is made up of irrefutable `let` statements
329+ let let_source = let_source_parent ( self . tcx , top, None ) ;
330+ irrefutable_let_patterns (
331+ cx. tcx ,
332+ top,
333+ let_source,
334+ chain_refutabilities. len ( ) ,
335+ top_expr. span ,
336+ ) ;
337+ return true ;
338+ }
339+ let lint_affix = |affix : & [ Option < ( Span , bool ) > ] , kind, suggestion| {
340+ let span_start = affix[ 0 ] . unwrap ( ) . 0 ;
341+ let span_end = affix. last ( ) . unwrap ( ) . unwrap ( ) . 0 ;
342+ let span = span_start. to ( span_end) ;
343+ let cnt = affix. len ( ) ;
344+ cx. tcx . struct_span_lint_hir ( IRREFUTABLE_LET_PATTERNS , top, span, |lint| {
345+ let s = pluralize ! ( cnt) ;
346+ let mut diag = lint. build ( & format ! ( "{kind} irrefutable pattern{s} in let chain" ) ) ;
347+ diag. note ( & format ! (
348+ "{these} pattern{s} will always match" ,
349+ these = pluralize!( "this" , cnt) ,
350+ ) ) ;
351+ diag. help ( & format ! (
352+ "consider moving {} {suggestion}" ,
353+ if cnt > 1 { "them" } else { "it" }
354+ ) ) ;
355+ diag. emit ( )
356+ } ) ;
357+ } ;
358+ if let Some ( until) = chain_refutabilities. iter ( ) . position ( |r| !matches ! ( * r, Some ( ( _, false ) ) ) ) && until > 0 {
359+ // The chain has a non-zero prefix of irrefutable `let` statements.
360+
361+ // Check if the let source is while, for there is no alternative place to put a prefix,
362+ // and we shouldn't lint.
363+ let let_source = let_source_parent ( self . tcx , top, None ) ;
364+ if !matches ! ( let_source, LetSource :: WhileLet ) {
365+ // Emit the lint
366+ let prefix = & chain_refutabilities[ ..until] ;
367+ lint_affix ( prefix, "leading" , "outside of the construct" ) ;
368+ }
369+ }
370+ if let Some ( from) = chain_refutabilities. iter ( ) . rposition ( |r| !matches ! ( * r, Some ( ( _, false ) ) ) ) && from != ( chain_refutabilities. len ( ) - 1 ) {
371+ // The chain has a non-empty suffix of irrefutable `let` statements
372+ let suffix = & chain_refutabilities[ from + 1 ..] ;
373+ lint_affix ( suffix, "trailing" , "into the body" ) ;
374+ }
375+ true
376+ }
377+
227378 fn check_irrefutable ( & self , pat : & ' tcx Pat < ' tcx > , origin : & str , sp : Option < Span > ) {
228379 let mut cx = self . new_cx ( pat. hir_id ) ;
229380
@@ -453,21 +604,33 @@ fn unreachable_pattern(tcx: TyCtxt<'_>, span: Span, id: HirId, catchall: Option<
453604}
454605
455606fn irrefutable_let_pattern ( tcx : TyCtxt < ' _ > , id : HirId , span : Span ) {
607+ let source = let_source ( tcx, id) ;
608+ irrefutable_let_patterns ( tcx, id, source, 1 , span) ;
609+ }
610+
611+ fn irrefutable_let_patterns (
612+ tcx : TyCtxt < ' _ > ,
613+ id : HirId ,
614+ source : LetSource ,
615+ count : usize ,
616+ span : Span ,
617+ ) {
456618 macro_rules! emit_diag {
457619 (
458620 $lint: expr,
459621 $source_name: expr,
460622 $note_sufix: expr,
461623 $help_sufix: expr
462624 ) => { {
463- let mut diag = $lint. build( concat!( "irrefutable " , $source_name, " pattern" ) ) ;
464- diag. note( concat!( "this pattern will always match, so the " , $note_sufix) ) ;
625+ let s = pluralize!( count) ;
626+ let these = pluralize!( "this" , count) ;
627+ let mut diag = $lint. build( & format!( "irrefutable {} pattern{s}" , $source_name) ) ;
628+ diag. note( & format!( "{these} pattern{s} will always match, so the {}" , $note_sufix) ) ;
465629 diag. help( concat!( "consider " , $help_sufix) ) ;
466630 diag. emit( )
467631 } } ;
468632 }
469633
470- let source = let_source ( tcx, id) ;
471634 let span = match source {
472635 LetSource :: LetElse ( span) => span,
473636 _ => span,
@@ -511,16 +674,11 @@ fn irrefutable_let_pattern(tcx: TyCtxt<'_>, id: HirId, span: Span) {
511674 } ) ;
512675}
513676
514- fn check_let_reachability < ' p , ' tcx > (
677+ fn is_let_irrefutable < ' p , ' tcx > (
515678 cx : & mut MatchCheckCtxt < ' p , ' tcx > ,
516679 pat_id : HirId ,
517680 pat : & ' p DeconstructedPat < ' p , ' tcx > ,
518- span : Span ,
519- ) {
520- if is_let_chain ( cx. tcx , pat_id) {
521- return ;
522- }
523-
681+ ) -> bool {
524682 let arms = [ MatchArm { pat, hir_id : pat_id, has_guard : false } ] ;
525683 let report = compute_match_usefulness ( & cx, & arms, pat_id, pat. ty ( ) ) ;
526684
@@ -529,10 +687,9 @@ fn check_let_reachability<'p, 'tcx>(
529687 // `is_uninhabited` check.
530688 report_arm_reachability ( & cx, & report) ;
531689
532- if report. non_exhaustiveness_witnesses . is_empty ( ) {
533- // The match is exhaustive, i.e. the `if let` pattern is irrefutable.
534- irrefutable_let_pattern ( cx. tcx , pat_id, span) ;
535- }
690+ // If the list of witnesses is empty, the match is exhaustive,
691+ // i.e. the `if let` pattern is irrefutable.
692+ report. non_exhaustiveness_witnesses . is_empty ( )
536693}
537694
538695/// Report unreachable arms, if any.
@@ -941,13 +1098,19 @@ fn let_source(tcx: TyCtxt<'_>, pat_id: HirId) -> LetSource {
9411098 let hir = tcx. hir ( ) ;
9421099
9431100 let parent = hir. get_parent_node ( pat_id) ;
1101+ let_source_parent ( tcx, parent, Some ( pat_id) )
1102+ }
1103+
1104+ fn let_source_parent ( tcx : TyCtxt < ' _ > , parent : HirId , pat_id : Option < HirId > ) -> LetSource {
1105+ let hir = tcx. hir ( ) ;
1106+
9441107 let parent_node = hir. get ( parent) ;
9451108
9461109 match parent_node {
9471110 hir:: Node :: Arm ( hir:: Arm {
9481111 guard : Some ( hir:: Guard :: IfLet ( & hir:: Pat { hir_id, .. } , _) ) ,
9491112 ..
950- } ) if hir_id == pat_id => {
1113+ } ) if Some ( hir_id) == pat_id => {
9511114 return LetSource :: IfLetGuard ;
9521115 }
9531116 hir:: Node :: Expr ( hir:: Expr { kind : hir:: ExprKind :: Let ( ..) , span, .. } ) => {
@@ -980,21 +1143,3 @@ fn let_source(tcx: TyCtxt<'_>, pat_id: HirId) -> LetSource {
9801143
9811144 LetSource :: GenericLet
9821145}
983-
984- // Since this function is called within a let context, it is reasonable to assume that any parent
985- // `&&` infers a let chain
986- fn is_let_chain ( tcx : TyCtxt < ' _ > , pat_id : HirId ) -> bool {
987- let hir = tcx. hir ( ) ;
988- let parent = hir. get_parent_node ( pat_id) ;
989- let parent_parent = hir. get_parent_node ( parent) ;
990- matches ! (
991- hir. get( parent_parent) ,
992- hir:: Node :: Expr (
993- hir:: Expr {
994- kind: hir:: ExprKind :: Binary ( Spanned { node: hir:: BinOpKind :: And , .. } , ..) ,
995- ..
996- } ,
997- ..
998- )
999- )
1000- }
0 commit comments