Skip to content

Commit d7a6365

Browse files
committed
Rewrite usefulness merging using SpanSet
`SpanSet` is heavily inspired from `DefIdForest`.
1 parent 170fae2 commit d7a6365

File tree

4 files changed

+122
-67
lines changed

4 files changed

+122
-67
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ fn report_arm_reachability<'p, 'tcx>(
402402
Useful(unreachables) if unreachables.is_empty() => {}
403403
// The arm is reachable, but contains unreachable subpatterns (from or-patterns).
404404
Useful(unreachables) => {
405-
let mut unreachables: Vec<_> = unreachables.iter().flatten().copied().collect();
405+
let mut unreachables: Vec<_> = unreachables.iter().collect();
406406
// Emit lints in the order in which they occur in the file.
407407
unreachables.sort_unstable();
408408
for span in unreachables {

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

Lines changed: 113 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,6 @@ use super::{Pat, PatKind};
311311
use super::{PatternFoldable, PatternFolder};
312312

313313
use rustc_data_structures::captures::Captures;
314-
use rustc_data_structures::fx::FxHashSet;
315314
use rustc_data_structures::sync::OnceCell;
316315

317316
use rustc_arena::TypedArena;
@@ -626,11 +625,81 @@ impl<'p, 'tcx> FromIterator<PatStack<'p, 'tcx>> for Matrix<'p, 'tcx> {
626625
}
627626
}
628627

628+
/// Represents a set of `Span`s closed under the containment relation. That is, if a `Span` is
629+
/// contained in the set then all `Span`s contained in it are also implicitly contained in the set.
630+
/// In particular this means that when intersecting two sets, taking the intersection of some span
631+
/// and one of its subspans returns the subspan, whereas a simple `HashSet` would have returned an
632+
/// empty intersection.
633+
/// It is assumed that two spans don't overlap without one being contained in the other; in other
634+
/// words, that the inclusion structure forms a tree and not a DAG.
635+
/// Operations on this do not need to be fast since it's only nonempty in the diagnostic path.
636+
#[derive(Debug, Clone, Default)]
637+
pub(crate) struct SpanSet {
638+
/// The minimal set of `Span`s required to represent the whole set. If A and B are `Span`s in
639+
/// the `SpanSet`, and A is a descendant of B, then only B will be in `root_spans`.
640+
/// Invariant: the spans are disjoint.
641+
root_spans: Vec<Span>,
642+
}
643+
644+
impl SpanSet {
645+
/// Creates an empty set.
646+
fn new() -> Self {
647+
Self::default()
648+
}
649+
650+
/// Tests whether the set is empty.
651+
pub(crate) fn is_empty(&self) -> bool {
652+
self.root_spans.is_empty()
653+
}
654+
655+
/// Iterate over the disjoint list of spans at the roots of this set.
656+
pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item = Span> + Captures<'a> {
657+
self.root_spans.iter().copied()
658+
}
659+
660+
/// Tests whether the set contains a given Span.
661+
fn contains(&self, span: Span) -> bool {
662+
self.iter().any(|root_span| root_span.contains(span))
663+
}
664+
665+
/// Add a span to the set if we know the span has no intersection in this set.
666+
fn push_nonintersecting(&mut self, new_span: Span) {
667+
self.root_spans.push(new_span);
668+
}
669+
670+
fn intersection_mut(&mut self, other: &Self) {
671+
if self.is_empty() || other.is_empty() {
672+
*self = Self::new();
673+
return;
674+
}
675+
// Those that were in `self` but not contained in `other`
676+
let mut leftover = SpanSet::new();
677+
// We keep the elements in `self` that are also in `other`.
678+
self.root_spans.retain(|span| {
679+
let retain = other.contains(*span);
680+
if !retain {
681+
leftover.root_spans.push(*span);
682+
}
683+
retain
684+
});
685+
// We keep the elements in `other` that are also in the original `self`. You might think
686+
// this is not needed because `self` already contains the intersection. But those aren't
687+
// just sets of things. If `self = [a]`, `other = [b]` and `a` contains `b`, then `b`
688+
// belongs in the intersection but we didn't catch it in the filtering above. We look at
689+
// `leftover` instead of the full original `self` to avoid duplicates.
690+
for span in other.iter() {
691+
if leftover.contains(span) {
692+
self.root_spans.push(span);
693+
}
694+
}
695+
}
696+
}
697+
629698
#[derive(Clone, Debug)]
630699
crate enum Usefulness<'tcx> {
631-
/// Carries, for each column in the matrix, a set of sub-branches that have been found to be
632-
/// unreachable. Used only in the presence of or-patterns, otherwise it stays empty.
633-
Useful(Vec<FxHashSet<Span>>),
700+
/// Pontentially carries a set of sub-branches that have been found to be unreachable. Used
701+
/// only in the presence of or-patterns, otherwise it stays empty.
702+
Useful(SpanSet),
634703
/// Carries a list of witnesses of non-exhaustiveness.
635704
UsefulWithWitness(Vec<Witness<'tcx>>),
636705
NotUseful,
@@ -640,7 +709,7 @@ impl<'tcx> Usefulness<'tcx> {
640709
fn new_useful(preference: WitnessPreference) -> Self {
641710
match preference {
642711
ConstructWitness => UsefulWithWitness(vec![Witness(vec![])]),
643-
LeaveOutWitness => Useful(vec![]),
712+
LeaveOutWitness => Useful(Default::default()),
644713
}
645714
}
646715

@@ -650,58 +719,55 @@ impl<'tcx> Usefulness<'tcx> {
650719

651720
/// When trying several branches and each returns a `Usefulness`, we need to combine the
652721
/// results together.
653-
fn merge(usefulnesses: impl Iterator<Item = (Self, Span)>, column_count: usize) -> Self {
654-
// If two branches have detected some unreachable sub-branches, we need to be careful. If
655-
// they were detected in columns that are not the current one, we want to keep only the
656-
// sub-branches that were unreachable in _all_ branches. Eg. in the following, the last
657-
// `true` is unreachable in the second branch of the first or-pattern, but not otherwise.
658-
// Therefore we don't want to lint that it is unreachable.
659-
//
722+
fn merge(usefulnesses: impl Iterator<Item = (Self, Span)>) -> Self {
723+
// If we have detected some unreachable sub-branches, we only want to keep them when they
724+
// were unreachable in _all_ branches. Eg. in the following, the last `true` is unreachable
725+
// in the second branch of the first or-pattern, but not otherwise. Therefore we don't want
726+
// to lint that it is unreachable.
660727
// ```
661728
// match (true, true) {
662729
// (true, true) => {}
663730
// (false | true, false | true) => {}
664731
// }
665732
// ```
666-
// If however the sub-branches come from the current column, they come from the inside of
667-
// the current or-pattern, and we want to keep them all. Eg. in the following, we _do_ want
668-
// to lint that the last `false` is unreachable.
733+
// Here however we _do_ want to lint that the last `false` is unreachable. So we don't want
734+
// to intersect the spans that come directly from the or-pattern, since each branch of the
735+
// or-pattern brings a new disjoint pattern.
669736
// ```
670737
// match None {
671738
// Some(false) => {}
672739
// None | Some(true | false) => {}
673740
// }
674741
// ```
675742

676-
// We keep track of sub-branches separately depending on whether they come from this column
677-
// or from others.
678-
let mut unreachables_this_column: FxHashSet<Span> = FxHashSet::default();
679-
let mut unreachables_other_columns: Vec<FxHashSet<Span>> = Vec::default();
680-
// Whether at least one branch is reachable.
681-
let mut any_is_useful = false;
743+
// Is `None` when no branch was useful. Will often be `Some(Spanset::new())` because the
744+
// sets are only non-empty in the diagnostic path.
745+
let mut unreachables: Option<SpanSet> = None;
746+
// In case of or-patterns we don't want to intersect subpatterns that come from the first
747+
// column. Invariant: contains a list of disjoint spans.
748+
let mut unreachables_this_column = Vec::new();
682749

683-
for (u, span) in usefulnesses {
750+
for (u, branch_span) in usefulnesses {
684751
match u {
685-
Useful(unreachables) => {
686-
if let Some((this_column, other_columns)) = unreachables.split_last() {
687-
// We keep the union of unreachables found in the first column.
688-
unreachables_this_column.extend(this_column);
689-
// We keep the intersection of unreachables found in other columns.
690-
if unreachables_other_columns.is_empty() {
691-
unreachables_other_columns = other_columns.to_vec();
692-
} else {
693-
unreachables_other_columns = unreachables_other_columns
694-
.into_iter()
695-
.zip(other_columns)
696-
.map(|(x, y)| x.intersection(&y).copied().collect())
697-
.collect();
752+
Useful(spans) if spans.is_empty() => {
753+
// Hot path: `spans` is only non-empty in the diagnostic path.
754+
unreachables = Some(SpanSet::new());
755+
}
756+
Useful(spans) => {
757+
for span in spans.iter() {
758+
if branch_span.contains(span) {
759+
unreachables_this_column.push(span)
698760
}
699761
}
700-
any_is_useful = true;
701-
}
702-
NotUseful => {
703-
unreachables_this_column.insert(span);
762+
if let Some(set) = &mut unreachables {
763+
if !set.is_empty() {
764+
set.intersection_mut(&spans);
765+
}
766+
} else {
767+
unreachables = Some(spans);
768+
}
704769
}
770+
NotUseful => unreachables_this_column.push(branch_span),
705771
UsefulWithWitness(_) => {
706772
bug!(
707773
"encountered or-pat in the expansion of `_` during exhaustiveness checking"
@@ -710,13 +776,13 @@ impl<'tcx> Usefulness<'tcx> {
710776
}
711777
}
712778

713-
if any_is_useful {
714-
let mut unreachables = if unreachables_other_columns.is_empty() {
715-
(0..column_count - 1).map(|_| FxHashSet::default()).collect()
716-
} else {
717-
unreachables_other_columns
718-
};
719-
unreachables.push(unreachables_this_column);
779+
if let Some(mut unreachables) = unreachables {
780+
for span in unreachables_this_column {
781+
// `unreachables` contained no spans from the first column, and
782+
// `unreachables_this_column` contains only disjoint spans. Therefore it is valid
783+
// to call `push_nonintersecting`.
784+
unreachables.push_nonintersecting(span);
785+
}
720786
Useful(unreachables)
721787
} else {
722788
NotUseful
@@ -752,23 +818,6 @@ impl<'tcx> Usefulness<'tcx> {
752818
};
753819
UsefulWithWitness(new_witnesses)
754820
}
755-
Useful(mut unreachables) => {
756-
if !unreachables.is_empty() {
757-
// When we apply a constructor, there are `arity` columns of the matrix that
758-
// corresponded to its arguments. All the unreachables found in these columns
759-
// will, after `apply`, come from the first column. So we take the union of all
760-
// the corresponding sets and put them in the first column.
761-
// Note that `arity` may be 0, in which case we just push a new empty set.
762-
let len = unreachables.len();
763-
let arity = ctor_wild_subpatterns.len();
764-
let mut unioned = FxHashSet::default();
765-
for set in unreachables.drain((len - arity)..) {
766-
unioned.extend(set)
767-
}
768-
unreachables.push(unioned);
769-
}
770-
Useful(unreachables)
771-
}
772821
x => x,
773822
}
774823
}
@@ -926,7 +975,7 @@ fn is_useful<'p, 'tcx>(
926975
}
927976
(u, span)
928977
});
929-
Usefulness::merge(usefulnesses, v.len())
978+
Usefulness::merge(usefulnesses)
930979
} else {
931980
v.head_ctor(cx)
932981
.split(pcx, Some(hir_id))

src/test/ui/or-patterns/exhaustiveness-unreachable-pattern.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ fn main() {
8181
match (true, None) {
8282
(true, Some(_)) => {}
8383
(false, Some(true)) => {}
84-
(true | false, None | Some(true // FIXME: should be unreachable
84+
(true | false, None | Some(true //~ ERROR unreachable
8585
| false)) => {}
8686
}
8787
macro_rules! t_or_f {

src/test/ui/or-patterns/exhaustiveness-unreachable-pattern.stderr

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ error: unreachable pattern
106106
LL | [true
107107
| ^^^^
108108

109+
error: unreachable pattern
110+
--> $DIR/exhaustiveness-unreachable-pattern.rs:84:36
111+
|
112+
LL | (true | false, None | Some(true
113+
| ^^^^
114+
109115
error: unreachable pattern
110116
--> $DIR/exhaustiveness-unreachable-pattern.rs:100:14
111117
|
@@ -130,5 +136,5 @@ error: unreachable pattern
130136
LL | | true,
131137
| ^^^^
132138

133-
error: aborting due to 21 previous errors
139+
error: aborting due to 22 previous errors
134140

0 commit comments

Comments
 (0)