712
712
//! I (Nadrieril) prefer to put new tests in `ui/pattern/usefulness` unless there's a specific
713
713
//! reason not to, for example if they crucially depend on a particular feature like `or_patterns`.
714
714
715
+ use rustc_index:: bit_set:: BitSet ;
715
716
use smallvec:: { smallvec, SmallVec } ;
716
717
use std:: fmt;
717
718
718
- use crate :: constructor:: { Constructor , ConstructorSet } ;
719
+ use crate :: constructor:: { Constructor , ConstructorSet , IntRange } ;
719
720
use crate :: pat:: { DeconstructedPat , PatOrWild , WitnessPat } ;
720
721
use crate :: { Captures , MatchArm , MatchCtxt , TypeCx } ;
721
722
@@ -911,6 +912,11 @@ struct MatrixRow<'p, Cx: TypeCx> {
911
912
/// [`compute_exhaustiveness_and_usefulness`] if the arm is found to be useful.
912
913
/// This is reset to `false` when specializing.
913
914
useful : bool ,
915
+ /// Tracks which rows above this one have an intersection with this one, i.e. such that there is
916
+ /// a value that matches both rows.
917
+ /// Note: Because of relevancy we may miss some intersections. The intersections we do find are
918
+ /// correct.
919
+ intersects : BitSet < usize > ,
914
920
}
915
921
916
922
impl < ' p , Cx : TypeCx > MatrixRow < ' p , Cx > {
@@ -938,6 +944,7 @@ impl<'p, Cx: TypeCx> MatrixRow<'p, Cx> {
938
944
parent_row : self . parent_row ,
939
945
is_under_guard : self . is_under_guard ,
940
946
useful : false ,
947
+ intersects : BitSet :: new_empty ( 0 ) , // Initialized in `Matrix::expand_and_push`.
941
948
} )
942
949
}
943
950
@@ -955,6 +962,7 @@ impl<'p, Cx: TypeCx> MatrixRow<'p, Cx> {
955
962
parent_row,
956
963
is_under_guard : self . is_under_guard ,
957
964
useful : false ,
965
+ intersects : BitSet :: new_empty ( 0 ) , // Initialized in `Matrix::expand_and_push`.
958
966
}
959
967
}
960
968
}
@@ -993,13 +1001,15 @@ struct Matrix<'p, Cx: TypeCx> {
993
1001
impl < ' p , Cx : TypeCx > Matrix < ' p , Cx > {
994
1002
/// Pushes a new row to the matrix. If the row starts with an or-pattern, this recursively
995
1003
/// expands it. Internal method, prefer [`Matrix::new`].
996
- fn expand_and_push ( & mut self , row : MatrixRow < ' p , Cx > ) {
1004
+ fn expand_and_push ( & mut self , mut row : MatrixRow < ' p , Cx > ) {
997
1005
if !row. is_empty ( ) && row. head ( ) . is_or_pat ( ) {
998
1006
// Expand nested or-patterns.
999
- for new_row in row. expand_or_pat ( ) {
1007
+ for mut new_row in row. expand_or_pat ( ) {
1008
+ new_row. intersects = BitSet :: new_empty ( self . rows . len ( ) ) ;
1000
1009
self . rows . push ( new_row) ;
1001
1010
}
1002
1011
} else {
1012
+ row. intersects = BitSet :: new_empty ( self . rows . len ( ) ) ;
1003
1013
self . rows . push ( row) ;
1004
1014
}
1005
1015
}
@@ -1019,9 +1029,10 @@ impl<'p, Cx: TypeCx> Matrix<'p, Cx> {
1019
1029
for ( row_id, arm) in arms. iter ( ) . enumerate ( ) {
1020
1030
let v = MatrixRow {
1021
1031
pats : PatStack :: from_pattern ( arm. pat ) ,
1022
- parent_row : row_id, // dummy, we won 't read it
1032
+ parent_row : row_id, // dummy, we don 't read it
1023
1033
is_under_guard : arm. has_guard ,
1024
1034
useful : false ,
1035
+ intersects : BitSet :: new_empty ( 0 ) , // Initialized in `Matrix::expand_and_push`.
1025
1036
} ;
1026
1037
matrix. expand_and_push ( v) ;
1027
1038
}
@@ -1317,6 +1328,83 @@ impl<Cx: TypeCx> WitnessMatrix<Cx> {
1317
1328
}
1318
1329
}
1319
1330
1331
+ /// Collect ranges that overlap like `lo..=overlap`/`overlap..=hi`. Must be called during
1332
+ /// exhaustiveness checking, if we find a singleton range after constructor splitting. This reuses
1333
+ /// row intersection information to only detect ranges that truly overlap.
1334
+ ///
1335
+ /// If two ranges overlapped, the split set will contain their intersection as a singleton.
1336
+ /// Specialization will then select rows that match the overlap, and exhaustiveness will compute
1337
+ /// which rows have an intersection that includes the overlap. That gives us all the info we need to
1338
+ /// compute overlapping ranges without false positives.
1339
+ ///
1340
+ /// We can however get false negatives because exhaustiveness does not explore all cases. See the
1341
+ /// section on relevancy at the top of the file.
1342
+ fn collect_overlapping_range_endpoints < ' p , Cx : TypeCx > (
1343
+ overlap_range : IntRange ,
1344
+ matrix : & Matrix < ' p , Cx > ,
1345
+ specialized_matrix : & Matrix < ' p , Cx > ,
1346
+ overlapping_range_endpoints : & mut Vec < OverlappingRanges < ' p , Cx > > ,
1347
+ ) {
1348
+ let overlap = overlap_range. lo ;
1349
+ // Ranges that look like `lo..=overlap`.
1350
+ let mut prefixes: SmallVec < [ _ ; 1 ] > = Default :: default ( ) ;
1351
+ // Ranges that look like `overlap..=hi`.
1352
+ let mut suffixes: SmallVec < [ _ ; 1 ] > = Default :: default ( ) ;
1353
+ // Iterate on patterns that contained `overlap`. We iterate on `specialized_matrix` which
1354
+ // contains only rows that matched the current `ctor` as well as accurate intersection
1355
+ // information. It doesn't contain the column that contains the range; that can be found in
1356
+ // `matrix`.
1357
+ for ( child_row_id, child_row) in specialized_matrix. rows ( ) . enumerate ( ) {
1358
+ let PatOrWild :: Pat ( pat) = matrix. rows [ child_row. parent_row ] . head ( ) else { continue } ;
1359
+ let Constructor :: IntRange ( this_range) = pat. ctor ( ) else { continue } ;
1360
+ // Don't lint when one of the ranges is a singleton.
1361
+ if this_range. is_singleton ( ) {
1362
+ continue ;
1363
+ }
1364
+ if this_range. lo == overlap {
1365
+ // `this_range` looks like `overlap..=this_range.hi`; it overlaps with any
1366
+ // ranges that look like `lo..=overlap`.
1367
+ if !prefixes. is_empty ( ) {
1368
+ let overlaps_with: Vec < _ > = prefixes
1369
+ . iter ( )
1370
+ . filter ( |& & ( other_child_row_id, _) | {
1371
+ child_row. intersects . contains ( other_child_row_id)
1372
+ } )
1373
+ . map ( |& ( _, pat) | pat)
1374
+ . collect ( ) ;
1375
+ if !overlaps_with. is_empty ( ) {
1376
+ overlapping_range_endpoints. push ( OverlappingRanges {
1377
+ pat,
1378
+ overlaps_on : overlap_range,
1379
+ overlaps_with,
1380
+ } ) ;
1381
+ }
1382
+ }
1383
+ suffixes. push ( ( child_row_id, pat) )
1384
+ } else if this_range. hi == overlap. plus_one ( ) {
1385
+ // `this_range` looks like `this_range.lo..=overlap`; it overlaps with any
1386
+ // ranges that look like `overlap..=hi`.
1387
+ if !suffixes. is_empty ( ) {
1388
+ let overlaps_with: Vec < _ > = suffixes
1389
+ . iter ( )
1390
+ . filter ( |& & ( other_child_row_id, _) | {
1391
+ child_row. intersects . contains ( other_child_row_id)
1392
+ } )
1393
+ . map ( |& ( _, pat) | pat)
1394
+ . collect ( ) ;
1395
+ if !overlaps_with. is_empty ( ) {
1396
+ overlapping_range_endpoints. push ( OverlappingRanges {
1397
+ pat,
1398
+ overlaps_on : overlap_range,
1399
+ overlaps_with,
1400
+ } ) ;
1401
+ }
1402
+ }
1403
+ prefixes. push ( ( child_row_id, pat) )
1404
+ }
1405
+ }
1406
+ }
1407
+
1320
1408
/// The core of the algorithm.
1321
1409
///
1322
1410
/// This recursively computes witnesses of the non-exhaustiveness of `matrix` (if any). Also tracks
@@ -1335,6 +1423,7 @@ impl<Cx: TypeCx> WitnessMatrix<Cx> {
1335
1423
fn compute_exhaustiveness_and_usefulness < ' a , ' p , Cx : TypeCx > (
1336
1424
mcx : MatchCtxt < ' a , ' p , Cx > ,
1337
1425
matrix : & mut Matrix < ' p , Cx > ,
1426
+ overlapping_range_endpoints : & mut Vec < OverlappingRanges < ' p , Cx > > ,
1338
1427
is_top_level : bool ,
1339
1428
) -> Result < WitnessMatrix < Cx > , Cx :: Error > {
1340
1429
debug_assert ! ( matrix. rows( ) . all( |r| r. len( ) == matrix. column_count( ) ) ) ;
@@ -1349,21 +1438,19 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
1349
1438
let Some ( ty) = matrix. head_ty ( ) else {
1350
1439
// The base case: there are no columns in the matrix. We are morally pattern-matching on ().
1351
1440
// A row is useful iff it has no (unguarded) rows above it.
1352
- for row in matrix. rows_mut ( ) {
1353
- // All rows are useful until they're not.
1354
- row. useful = true ;
1355
- // When there's an unguarded row, the match is exhaustive and any subsequent row is not
1356
- // useful.
1357
- if !row. is_under_guard {
1358
- return Ok ( WitnessMatrix :: empty ( ) ) ;
1359
- }
1441
+ let mut useful = true ; // Whether the next row is useful.
1442
+ for ( i, row) in matrix. rows_mut ( ) . enumerate ( ) {
1443
+ row. useful = useful;
1444
+ row. intersects . insert_range ( 0 ..i) ;
1445
+ // The next rows stays useful if this one is under a guard.
1446
+ useful &= row. is_under_guard ;
1360
1447
}
1361
- // No (unguarded) rows, so the match is not exhaustive. We return a new witness unless
1362
- // irrelevant.
1363
- return if matrix. wildcard_row_is_relevant {
1448
+ return if useful && matrix. wildcard_row_is_relevant {
1449
+ // The wildcard row is useful; the match is non-exhaustive.
1364
1450
Ok ( WitnessMatrix :: unit_witness ( ) )
1365
1451
} else {
1366
- // We choose to not report anything here; see at the top for details.
1452
+ // Either the match is exhaustive, or we choose not to report anything because of
1453
+ // relevancy. See at the top for details.
1367
1454
Ok ( WitnessMatrix :: empty ( ) )
1368
1455
} ;
1369
1456
} ;
@@ -1416,18 +1503,47 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
1416
1503
let ctor_is_relevant = matches ! ( ctor, Constructor :: Missing ) || missing_ctors. is_empty ( ) ;
1417
1504
let mut spec_matrix = matrix. specialize_constructor ( pcx, & ctor, ctor_is_relevant) ;
1418
1505
let mut witnesses = ensure_sufficient_stack ( || {
1419
- compute_exhaustiveness_and_usefulness ( mcx, & mut spec_matrix, false )
1506
+ compute_exhaustiveness_and_usefulness (
1507
+ mcx,
1508
+ & mut spec_matrix,
1509
+ overlapping_range_endpoints,
1510
+ false ,
1511
+ )
1420
1512
} ) ?;
1421
1513
1422
1514
// Transform witnesses for `spec_matrix` into witnesses for `matrix`.
1423
1515
witnesses. apply_constructor ( pcx, & missing_ctors, & ctor, report_individual_missing_ctors) ;
1424
1516
// Accumulate the found witnesses.
1425
1517
ret. extend ( witnesses) ;
1426
1518
1427
- // A parent row is useful if any of its children is.
1428
1519
for child_row in spec_matrix. rows ( ) {
1429
- let parent_row = & mut matrix. rows [ child_row. parent_row ] ;
1430
- parent_row. useful = parent_row. useful || child_row. useful ;
1520
+ let parent_row_id = child_row. parent_row ;
1521
+ let parent_row = & mut matrix. rows [ parent_row_id] ;
1522
+ // A parent row is useful if any of its children is.
1523
+ parent_row. useful |= child_row. useful ;
1524
+ for child_intersection in child_row. intersects . iter ( ) {
1525
+ // Convert the intersecting ids into ids for the parent matrix.
1526
+ let parent_intersection = spec_matrix. rows [ child_intersection] . parent_row ;
1527
+ // Note: self-intersection can happen with or-patterns.
1528
+ if parent_intersection != parent_row_id {
1529
+ parent_row. intersects . insert ( parent_intersection) ;
1530
+ }
1531
+ }
1532
+ }
1533
+
1534
+ // Detect ranges that overlap on their endpoints.
1535
+ if let Constructor :: IntRange ( overlap_range) = ctor {
1536
+ if overlap_range. is_singleton ( )
1537
+ && spec_matrix. rows . len ( ) >= 2
1538
+ && spec_matrix. rows . iter ( ) . any ( |row| !row. intersects . is_empty ( ) )
1539
+ {
1540
+ collect_overlapping_range_endpoints (
1541
+ overlap_range,
1542
+ matrix,
1543
+ & spec_matrix,
1544
+ overlapping_range_endpoints,
1545
+ ) ;
1546
+ }
1431
1547
}
1432
1548
}
1433
1549
@@ -1453,13 +1569,23 @@ pub enum Usefulness<'p, Cx: TypeCx> {
1453
1569
Redundant ,
1454
1570
}
1455
1571
1572
+ /// Indicates that the range `pat` overlapped with all the ranges in `overlaps_with`, where the
1573
+ /// range they overlapped over is `overlaps_on`. We only detect singleton overlaps.
1574
+ #[ derive( Clone , Debug ) ]
1575
+ pub struct OverlappingRanges < ' p , Cx : TypeCx > {
1576
+ pub pat : & ' p DeconstructedPat < ' p , Cx > ,
1577
+ pub overlaps_on : IntRange ,
1578
+ pub overlaps_with : Vec < & ' p DeconstructedPat < ' p , Cx > > ,
1579
+ }
1580
+
1456
1581
/// The output of checking a match for exhaustiveness and arm usefulness.
1457
1582
pub struct UsefulnessReport < ' p , Cx : TypeCx > {
1458
1583
/// For each arm of the input, whether that arm is useful after the arms above it.
1459
1584
pub arm_usefulness : Vec < ( MatchArm < ' p , Cx > , Usefulness < ' p , Cx > ) > ,
1460
1585
/// If the match is exhaustive, this is empty. If not, this contains witnesses for the lack of
1461
1586
/// exhaustiveness.
1462
1587
pub non_exhaustiveness_witnesses : Vec < WitnessPat < Cx > > ,
1588
+ pub overlapping_range_endpoints : Vec < OverlappingRanges < ' p , Cx > > ,
1463
1589
}
1464
1590
1465
1591
/// Computes whether a match is exhaustive and which of its arms are useful.
@@ -1470,9 +1596,14 @@ pub fn compute_match_usefulness<'p, Cx: TypeCx>(
1470
1596
scrut_ty : Cx :: Ty ,
1471
1597
scrut_validity : ValidityConstraint ,
1472
1598
) -> Result < UsefulnessReport < ' p , Cx > , Cx :: Error > {
1599
+ let mut overlapping_range_endpoints = Vec :: new ( ) ;
1473
1600
let mut matrix = Matrix :: new ( arms, scrut_ty, scrut_validity) ;
1474
- let non_exhaustiveness_witnesses =
1475
- compute_exhaustiveness_and_usefulness ( cx, & mut matrix, true ) ?;
1601
+ let non_exhaustiveness_witnesses = compute_exhaustiveness_and_usefulness (
1602
+ cx,
1603
+ & mut matrix,
1604
+ & mut overlapping_range_endpoints,
1605
+ true ,
1606
+ ) ?;
1476
1607
1477
1608
let non_exhaustiveness_witnesses: Vec < _ > = non_exhaustiveness_witnesses. single_column ( ) ;
1478
1609
let arm_usefulness: Vec < _ > = arms
@@ -1489,5 +1620,10 @@ pub fn compute_match_usefulness<'p, Cx: TypeCx>(
1489
1620
( arm, usefulness)
1490
1621
} )
1491
1622
. collect ( ) ;
1492
- Ok ( UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses } )
1623
+
1624
+ Ok ( UsefulnessReport {
1625
+ arm_usefulness,
1626
+ non_exhaustiveness_witnesses,
1627
+ overlapping_range_endpoints,
1628
+ } )
1493
1629
}
0 commit comments