Skip to content

Commit 74ce70e

Browse files
committed
Abstract over per-column pattern traversal
1 parent 26f340a commit 74ce70e

File tree

1 file changed

+79
-34
lines changed

1 file changed

+79
-34
lines changed

compiler/rustc_mir_build/src/thir/pattern/usefulness.rs

Lines changed: 79 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,9 @@
307307
308308
use self::ArmType::*;
309309
use self::Usefulness::*;
310-
use super::deconstruct_pat::{Constructor, ConstructorSet, DeconstructedPat, WitnessPat};
310+
use super::deconstruct_pat::{
311+
Constructor, ConstructorSet, DeconstructedPat, SplitConstructorSet, WitnessPat,
312+
};
311313
use crate::errors::{NonExhaustiveOmittedPattern, Uncovered};
312314

313315
use rustc_data_structures::captures::Captures;
@@ -875,19 +877,85 @@ fn is_useful<'p, 'tcx>(
875877
ret
876878
}
877879

880+
/// A column of patterns in the matrix, where a column is the intuitive notion of "subpatterns that
881+
/// inspect the same subvalue".
882+
/// This is used to traverse patterns column-by-column for lints. Despite similarities with
883+
/// `is_useful`, this is a different traversal. Notably this is linear in the depth of patterns,
884+
/// whereas `is_useful` is worst-case exponential (exhaustiveness is NP-complete).
885+
#[derive(Debug)]
886+
struct PatternColumn<'p, 'tcx> {
887+
patterns: Vec<&'p DeconstructedPat<'p, 'tcx>>,
888+
}
889+
890+
impl<'p, 'tcx> PatternColumn<'p, 'tcx> {
891+
fn new(patterns: Vec<&'p DeconstructedPat<'p, 'tcx>>) -> Self {
892+
Self { patterns }
893+
}
894+
895+
fn is_empty(&self) -> bool {
896+
self.patterns.is_empty()
897+
}
898+
fn head_ty(&self) -> Ty<'tcx> {
899+
self.patterns[0].ty()
900+
}
901+
902+
fn analyze_ctors(&self, pcx: &PatCtxt<'_, 'p, 'tcx>) -> SplitConstructorSet<'tcx> {
903+
let column_ctors = self.patterns.iter().map(|p| p.ctor());
904+
ConstructorSet::for_ty(pcx.cx, pcx.ty).split(pcx, column_ctors)
905+
}
906+
907+
/// Does specialization: given a constructor, this takes the patterns from the column that match
908+
/// the constructor, and outputs their fields.
909+
/// This returns one column per field of the constructor. The normally all have the same length
910+
/// (the number of patterns in `self` that matched `ctor`), except that we expand or-patterns
911+
/// which may change the lengths.
912+
fn specialize(&self, pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: &Constructor<'tcx>) -> Vec<Self> {
913+
let arity = ctor.arity(pcx);
914+
if arity == 0 {
915+
return Vec::new();
916+
}
917+
918+
// We specialize the column by `ctor`. This gives us `arity`-many columns of patterns. These
919+
// columns may have different lengths in the presence of or-patterns (this is why we can't
920+
// reuse `Matrix`).
921+
let mut specialized_columns: Vec<_> =
922+
(0..arity).map(|_| Self { patterns: Vec::new() }).collect();
923+
let relevant_patterns =
924+
self.patterns.iter().filter(|pat| ctor.is_covered_by(pcx, pat.ctor()));
925+
for pat in relevant_patterns {
926+
let specialized = pat.specialize(pcx, &ctor);
927+
for (subpat, column) in specialized.iter().zip(&mut specialized_columns) {
928+
if subpat.is_or_pat() {
929+
column.patterns.extend(subpat.iter_fields())
930+
} else {
931+
column.patterns.push(subpat)
932+
}
933+
}
934+
}
935+
936+
assert!(
937+
!specialized_columns[0].is_empty(),
938+
"ctor {ctor:?} was listed as present but isn't;
939+
there is an inconsistency between `Constructor::is_covered_by` and `ConstructorSet::split`"
940+
);
941+
specialized_columns
942+
}
943+
}
944+
878945
/// Traverse the patterns to collect any variants of a non_exhaustive enum that fail to be mentioned
879-
/// in a given column. This traverses patterns column-by-column, where a column is the intuitive
880-
/// notion of "subpatterns that inspect the same subvalue".
881-
/// Despite similarities with `is_useful`, this traversal is different. Notably this is linear in the
882-
/// depth of patterns, whereas `is_useful` is worst-case exponential (exhaustiveness is NP-complete).
946+
/// in a given column.
947+
#[instrument(level = "debug", skip(cx), ret)]
883948
fn collect_nonexhaustive_missing_variants<'p, 'tcx>(
884949
cx: &MatchCheckCtxt<'p, 'tcx>,
885-
column: &[&DeconstructedPat<'p, 'tcx>],
950+
column: &PatternColumn<'p, 'tcx>,
886951
) -> Vec<WitnessPat<'tcx>> {
887-
let ty = column[0].ty();
952+
if column.is_empty() {
953+
return Vec::new();
954+
}
955+
let ty = column.head_ty();
888956
let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level: false };
889957

890-
let set = ConstructorSet::for_ty(pcx.cx, pcx.ty).split(pcx, column.iter().map(|p| p.ctor()));
958+
let set = column.analyze_ctors(pcx);
891959
if set.present.is_empty() {
892960
// We can't consistently handle the case where no constructors are present (since this would
893961
// require digging deep through any type in case there's a non_exhaustive enum somewhere),
@@ -908,35 +976,11 @@ fn collect_nonexhaustive_missing_variants<'p, 'tcx>(
908976

909977
// Recurse into the fields.
910978
for ctor in set.present {
911-
let arity = ctor.arity(pcx);
912-
if arity == 0 {
913-
continue;
914-
}
915-
916-
// We specialize the column by `ctor`. This gives us `arity`-many columns of patterns. These
917-
// columns may have different lengths in the presence of or-patterns (this is why we can't
918-
// reuse `Matrix`).
919-
let mut specialized_columns: Vec<Vec<_>> = (0..arity).map(|_| Vec::new()).collect();
920-
let relevant_patterns = column.iter().filter(|pat| ctor.is_covered_by(pcx, pat.ctor()));
921-
for pat in relevant_patterns {
922-
let specialized = pat.specialize(pcx, &ctor);
923-
for (subpat, sub_column) in specialized.iter().zip(&mut specialized_columns) {
924-
if subpat.is_or_pat() {
925-
sub_column.extend(subpat.iter_fields())
926-
} else {
927-
sub_column.push(subpat)
928-
}
929-
}
930-
}
931-
debug_assert!(
932-
!specialized_columns[0].is_empty(),
933-
"ctor {ctor:?} was listed as present but isn't"
934-
);
935-
979+
let specialized_columns = column.specialize(pcx, &ctor);
936980
let wild_pat = WitnessPat::wild_from_ctor(pcx, ctor);
937981
for (i, col_i) in specialized_columns.iter().enumerate() {
938982
// Compute witnesses for each column.
939-
let wits_for_col_i = collect_nonexhaustive_missing_variants(cx, col_i.as_slice());
983+
let wits_for_col_i = collect_nonexhaustive_missing_variants(cx, col_i);
940984
// For each witness, we build a new pattern in the shape of `ctor(_, _, wit, _, _)`,
941985
// adding enough wildcards to match `arity`.
942986
for wit in wits_for_col_i {
@@ -1029,6 +1073,7 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>(
10291073
)
10301074
{
10311075
let pat_column = arms.iter().flat_map(|arm| arm.pat.flatten_or_pat()).collect::<Vec<_>>();
1076+
let pat_column = PatternColumn::new(pat_column);
10321077
let witnesses = collect_nonexhaustive_missing_variants(cx, &pat_column);
10331078

10341079
if !witnesses.is_empty() {

0 commit comments

Comments
 (0)