Skip to content

Commit beecd93

Browse files
committed
Abstract over per-column pattern traversal
1 parent aa91057 commit beecd93

File tree

1 file changed

+77
-36
lines changed

1 file changed

+77
-36
lines changed

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

+77-36
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,22 +877,84 @@ 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) -> Option<Ty<'tcx>> {
899+
self.patterns.get(0).map(|p| p.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-
if column.is_empty() {
952+
let Some(ty) = column.head_ty() else {
888953
return Vec::new();
889-
}
890-
let ty = column[0].ty();
954+
};
891955
let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level: false };
892956

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

912976
// Recurse into the fields.
913977
for ctor in set.present {
914-
let arity = ctor.arity(pcx);
915-
if arity == 0 {
916-
continue;
917-
}
918-
919-
// We specialize the column by `ctor`. This gives us `arity`-many columns of patterns. These
920-
// columns may have different lengths in the presence of or-patterns (this is why we can't
921-
// reuse `Matrix`).
922-
let mut specialized_columns: Vec<Vec<_>> = (0..arity).map(|_| Vec::new()).collect();
923-
let relevant_patterns = column.iter().filter(|pat| ctor.is_covered_by(pcx, pat.ctor()));
924-
for pat in relevant_patterns {
925-
let specialized = pat.specialize(pcx, &ctor);
926-
for (subpat, sub_column) in specialized.iter().zip(&mut specialized_columns) {
927-
if subpat.is_or_pat() {
928-
sub_column.extend(subpat.iter_fields())
929-
} else {
930-
sub_column.push(subpat)
931-
}
932-
}
933-
}
934-
debug_assert!(
935-
!specialized_columns[0].is_empty(),
936-
"ctor {ctor:?} was listed as present but isn't"
937-
);
938-
978+
let specialized_columns = column.specialize(pcx, &ctor);
939979
let wild_pat = WitnessPat::wild_from_ctor(pcx, ctor);
940980
for (i, col_i) in specialized_columns.iter().enumerate() {
941981
// Compute witnesses for each column.
942-
let wits_for_col_i = collect_nonexhaustive_missing_variants(cx, col_i.as_slice());
982+
let wits_for_col_i = collect_nonexhaustive_missing_variants(cx, col_i);
943983
// For each witness, we build a new pattern in the shape of `ctor(_, _, wit, _, _)`,
944984
// adding enough wildcards to match `arity`.
945985
for wit in wits_for_col_i {
@@ -1032,6 +1072,7 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>(
10321072
)
10331073
{
10341074
let pat_column = arms.iter().flat_map(|arm| arm.pat.flatten_or_pat()).collect::<Vec<_>>();
1075+
let pat_column = PatternColumn::new(pat_column);
10351076
let witnesses = collect_nonexhaustive_missing_variants(cx, &pat_column);
10361077

10371078
if !witnesses.is_empty() {

0 commit comments

Comments
 (0)