@@ -158,7 +158,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
158
158
self . check_patterns ( pat, Refutable ) ;
159
159
let mut cx = self . new_cx ( scrutinee. hir_id ) ;
160
160
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) ;
162
162
}
163
163
164
164
fn check_match (
@@ -176,7 +176,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
176
176
if let Some ( hir:: Guard :: IfLet ( ref pat, _) ) = arm. guard {
177
177
self . check_patterns ( pat, Refutable ) ;
178
178
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 ( ) ) ;
180
180
}
181
181
}
182
182
@@ -224,6 +224,157 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
224
224
}
225
225
}
226
226
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
+
227
378
fn check_irrefutable ( & self , pat : & ' tcx Pat < ' tcx > , origin : & str , sp : Option < Span > ) {
228
379
let mut cx = self . new_cx ( pat. hir_id ) ;
229
380
@@ -453,21 +604,33 @@ fn unreachable_pattern(tcx: TyCtxt<'_>, span: Span, id: HirId, catchall: Option<
453
604
}
454
605
455
606
fn 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
+ ) {
456
618
macro_rules! emit_diag {
457
619
(
458
620
$lint: expr,
459
621
$source_name: expr,
460
622
$note_sufix: expr,
461
623
$help_sufix: expr
462
624
) => { {
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) ) ;
465
629
diag. help( concat!( "consider " , $help_sufix) ) ;
466
630
diag. emit( )
467
631
} } ;
468
632
}
469
633
470
- let source = let_source ( tcx, id) ;
471
634
let span = match source {
472
635
LetSource :: LetElse ( span) => span,
473
636
_ => span,
@@ -511,16 +674,11 @@ fn irrefutable_let_pattern(tcx: TyCtxt<'_>, id: HirId, span: Span) {
511
674
} ) ;
512
675
}
513
676
514
- fn check_let_reachability < ' p , ' tcx > (
677
+ fn is_let_irrefutable < ' p , ' tcx > (
515
678
cx : & mut MatchCheckCtxt < ' p , ' tcx > ,
516
679
pat_id : HirId ,
517
680
pat : & ' p DeconstructedPat < ' p , ' tcx > ,
518
- span : Span ,
519
- ) {
520
- if is_let_chain ( cx. tcx , pat_id) {
521
- return ;
522
- }
523
-
681
+ ) -> bool {
524
682
let arms = [ MatchArm { pat, hir_id : pat_id, has_guard : false } ] ;
525
683
let report = compute_match_usefulness ( & cx, & arms, pat_id, pat. ty ( ) ) ;
526
684
@@ -529,10 +687,9 @@ fn check_let_reachability<'p, 'tcx>(
529
687
// `is_uninhabited` check.
530
688
report_arm_reachability ( & cx, & report) ;
531
689
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 ( )
536
693
}
537
694
538
695
/// Report unreachable arms, if any.
@@ -941,13 +1098,19 @@ fn let_source(tcx: TyCtxt<'_>, pat_id: HirId) -> LetSource {
941
1098
let hir = tcx. hir ( ) ;
942
1099
943
1100
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
+
944
1107
let parent_node = hir. get ( parent) ;
945
1108
946
1109
match parent_node {
947
1110
hir:: Node :: Arm ( hir:: Arm {
948
1111
guard : Some ( hir:: Guard :: IfLet ( & hir:: Pat { hir_id, .. } , _) ) ,
949
1112
..
950
- } ) if hir_id == pat_id => {
1113
+ } ) if Some ( hir_id) == pat_id => {
951
1114
return LetSource :: IfLetGuard ;
952
1115
}
953
1116
hir:: Node :: Expr ( hir:: Expr { kind : hir:: ExprKind :: Let ( ..) , span, .. } ) => {
@@ -980,21 +1143,3 @@ fn let_source(tcx: TyCtxt<'_>, pat_id: HirId) -> LetSource {
980
1143
981
1144
LetSource :: GenericLet
982
1145
}
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