Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e20cb77

Browse files
committedOct 12, 2023
Auto merge of #116391 - Nadrieril:constructorset, r=cjgillot
exhaustiveness: Rework constructor splitting `SplitWildcard` was pretty opaque. I replaced it with a more legible abstraction: `ConstructorSet` represents the set of constructors for patterns of a given type. This clarifies responsibilities: `ConstructorSet` handles one clear task, and diagnostic-related shenanigans can be done separately. I'm quite excited, I had has this in mind for years but could never quite introduce it. This opens up possibilities, including type-specific optimisations (like using a `FxHashSet` to collect enum variants, which had been [hackily attempted some years ago](#76918)), my one-pass rewrite (#116042), and future librarification.
2 parents df4379b + c1b29b3 commit e20cb77

File tree

4 files changed

+726
-581
lines changed

4 files changed

+726
-581
lines changed
 

‎compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs

Lines changed: 615 additions & 502 deletions
Large diffs are not rendered by default.

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

Lines changed: 50 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@
307307
308308
use self::ArmType::*;
309309
use self::Usefulness::*;
310-
use super::deconstruct_pat::{Constructor, DeconstructedPat, Fields, SplitWildcard};
310+
use super::deconstruct_pat::{Constructor, ConstructorSet, DeconstructedPat, Fields};
311311
use crate::errors::{NonExhaustiveOmittedPattern, Uncovered};
312312

313313
use rustc_data_structures::captures::Captures;
@@ -368,8 +368,6 @@ pub(super) struct PatCtxt<'a, 'p, 'tcx> {
368368
/// Whether the current pattern is the whole pattern as found in a match arm, or if it's a
369369
/// subpattern.
370370
pub(super) is_top_level: bool,
371-
/// Whether the current pattern is from a `non_exhaustive` enum.
372-
pub(super) is_non_exhaustive: bool,
373371
}
374372

375373
impl<'a, 'p, 'tcx> fmt::Debug for PatCtxt<'a, 'p, 'tcx> {
@@ -616,62 +614,41 @@ impl<'p, 'tcx> Usefulness<'p, 'tcx> {
616614
WithWitnesses(ref witnesses) if witnesses.is_empty() => self,
617615
WithWitnesses(witnesses) => {
618616
let new_witnesses = if let Constructor::Missing { .. } = ctor {
617+
let mut missing = ConstructorSet::for_ty(pcx.cx, pcx.ty)
618+
.compute_missing(pcx, matrix.heads().map(DeconstructedPat::ctor));
619+
if missing.iter().any(|c| c.is_non_exhaustive()) {
620+
// We only report `_` here; listing other constructors would be redundant.
621+
missing = vec![Constructor::NonExhaustive];
622+
}
623+
619624
// We got the special `Missing` constructor, so each of the missing constructors
620-
// gives a new pattern that is not caught by the match. We list those patterns.
621-
if pcx.is_non_exhaustive {
622-
witnesses
623-
.into_iter()
624-
// Here we don't want the user to try to list all variants, we want them to add
625-
// a wildcard, so we only suggest that.
626-
.map(|witness| {
627-
witness.apply_constructor(pcx, &Constructor::NonExhaustive)
628-
})
629-
.collect()
630-
} else {
631-
let mut split_wildcard = SplitWildcard::new(pcx);
632-
split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor));
633-
634-
// This lets us know if we skipped any variants because they are marked
635-
// `doc(hidden)` or they are unstable feature gate (only stdlib types).
636-
let mut hide_variant_show_wild = false;
637-
// Construct for each missing constructor a "wild" version of this
638-
// constructor, that matches everything that can be built with
639-
// it. For example, if `ctor` is a `Constructor::Variant` for
640-
// `Option::Some`, we get the pattern `Some(_)`.
641-
let mut new_patterns: Vec<DeconstructedPat<'_, '_>> = split_wildcard
642-
.iter_missing(pcx)
643-
.filter_map(|missing_ctor| {
644-
// Check if this variant is marked `doc(hidden)`
645-
if missing_ctor.is_doc_hidden_variant(pcx)
646-
|| missing_ctor.is_unstable_variant(pcx)
647-
{
648-
hide_variant_show_wild = true;
649-
return None;
650-
}
651-
Some(DeconstructedPat::wild_from_ctor(pcx, missing_ctor.clone()))
652-
})
653-
.collect();
654-
655-
if hide_variant_show_wild {
656-
new_patterns.push(DeconstructedPat::wildcard(pcx.ty, pcx.span));
657-
}
658-
659-
witnesses
660-
.into_iter()
661-
.flat_map(|witness| {
662-
new_patterns.iter().map(move |pat| {
663-
Witness(
664-
witness
665-
.0
666-
.iter()
667-
.chain(once(pat))
668-
.map(DeconstructedPat::clone_and_forget_reachability)
669-
.collect(),
670-
)
671-
})
625+
// gives a new pattern that is not caught by the match.
626+
// We construct for each missing constructor a version of this constructor with
627+
// wildcards for fields, i.e. that matches everything that can be built with it.
628+
// For example, if `ctor` is a `Constructor::Variant` for `Option::Some`, we get
629+
// the pattern `Some(_)`.
630+
let new_patterns: Vec<DeconstructedPat<'_, '_>> = missing
631+
.into_iter()
632+
.map(|missing_ctor| {
633+
DeconstructedPat::wild_from_ctor(pcx, missing_ctor.clone())
634+
})
635+
.collect();
636+
637+
witnesses
638+
.into_iter()
639+
.flat_map(|witness| {
640+
new_patterns.iter().map(move |pat| {
641+
Witness(
642+
witness
643+
.0
644+
.iter()
645+
.chain(once(pat))
646+
.map(DeconstructedPat::clone_and_forget_reachability)
647+
.collect(),
648+
)
672649
})
673-
.collect()
674-
}
650+
})
651+
.collect()
675652
} else {
676653
witnesses
677654
.into_iter()
@@ -844,9 +821,8 @@ fn is_useful<'p, 'tcx>(
844821
ty = row.head().ty();
845822
}
846823
}
847-
let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty);
848824
debug!("v.head: {:?}, v.span: {:?}", v.head(), v.head().span());
849-
let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level, is_non_exhaustive };
825+
let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level };
850826

851827
let v_ctor = v.head().ctor();
852828
debug!(?v_ctor);
@@ -861,7 +837,8 @@ fn is_useful<'p, 'tcx>(
861837
}
862838
// We split the head constructor of `v`.
863839
let split_ctors = v_ctor.split(pcx, matrix.heads().map(DeconstructedPat::ctor));
864-
let is_non_exhaustive_and_wild = is_non_exhaustive && v_ctor.is_wildcard();
840+
let is_non_exhaustive_and_wild =
841+
cx.is_foreign_non_exhaustive_enum(ty) && v_ctor.is_wildcard();
865842
// For each constructor, we compute whether there's a value that starts with it that would
866843
// witness the usefulness of `v`.
867844
let start_matrix = &matrix;
@@ -895,27 +872,21 @@ fn is_useful<'p, 'tcx>(
895872
&& usefulness.is_useful() && matches!(witness_preference, RealArm)
896873
&& matches!(
897874
&ctor,
898-
Constructor::Missing { nonexhaustive_enum_missing_real_variants: true }
875+
Constructor::Missing { nonexhaustive_enum_missing_visible_variants: true }
899876
)
900877
{
901-
let patterns = {
902-
let mut split_wildcard = SplitWildcard::new(pcx);
903-
split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor));
904-
// Construct for each missing constructor a "wild" version of this
905-
// constructor, that matches everything that can be built with
906-
// it. For example, if `ctor` is a `Constructor::Variant` for
907-
// `Option::Some`, we get the pattern `Some(_)`.
908-
split_wildcard
909-
.iter_missing(pcx)
910-
// Filter out the `NonExhaustive` because we want to list only real
911-
// variants. Also remove any unstable feature gated variants.
912-
// Because of how we computed `nonexhaustive_enum_missing_real_variants`,
913-
// this will not return an empty `Vec`.
914-
.filter(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx)))
915-
.cloned()
916-
.map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor))
917-
.collect::<Vec<_>>()
918-
};
878+
let missing = ConstructorSet::for_ty(pcx.cx, pcx.ty)
879+
.compute_missing(pcx, matrix.heads().map(DeconstructedPat::ctor));
880+
// Construct for each missing constructor a "wild" version of this constructor, that
881+
// matches everything that can be built with it. For example, if `ctor` is a
882+
// `Constructor::Variant` for `Option::Some`, we get the pattern `Some(_)`.
883+
let patterns = missing
884+
.into_iter()
885+
// Because of how we computed `nonexhaustive_enum_missing_visible_variants`,
886+
// this will not return an empty `Vec`.
887+
.filter(|c| !(matches!(c, Constructor::NonExhaustive | Constructor::Hidden)))
888+
.map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor))
889+
.collect::<Vec<_>>();
919890

920891
// Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns`
921892
// is not exhaustive enough.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#![feature(never_type)]
2+
#![feature(exhaustive_patterns)]
3+
#![deny(unreachable_patterns)]
4+
5+
fn main() {}
6+
7+
fn foo(nevers: &[!]) {
8+
match nevers {
9+
&[] => (),
10+
};
11+
12+
match nevers {
13+
&[] => (),
14+
&[_] => (), //~ ERROR unreachable pattern
15+
&[_, _, ..] => (), //~ ERROR unreachable pattern
16+
};
17+
18+
match nevers {
19+
//~^ ERROR non-exhaustive patterns: `&[]` not covered
20+
&[_] => (), //~ ERROR unreachable pattern
21+
};
22+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
error: unreachable pattern
2+
--> $DIR/slice_of_empty.rs:14:9
3+
|
4+
LL | &[_] => (),
5+
| ^^^^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/slice_of_empty.rs:3:9
9+
|
10+
LL | #![deny(unreachable_patterns)]
11+
| ^^^^^^^^^^^^^^^^^^^^
12+
13+
error: unreachable pattern
14+
--> $DIR/slice_of_empty.rs:15:9
15+
|
16+
LL | &[_, _, ..] => (),
17+
| ^^^^^^^^^^^
18+
19+
error: unreachable pattern
20+
--> $DIR/slice_of_empty.rs:20:9
21+
|
22+
LL | &[_] => (),
23+
| ^^^^
24+
25+
error[E0004]: non-exhaustive patterns: `&[]` not covered
26+
--> $DIR/slice_of_empty.rs:18:11
27+
|
28+
LL | match nevers {
29+
| ^^^^^^ pattern `&[]` not covered
30+
|
31+
= note: the matched value is of type `&[!]`
32+
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
33+
|
34+
LL | &[_] => (), &[] => todo!(),
35+
| ++++++++++++++++
36+
37+
error: aborting due to 4 previous errors
38+
39+
For more information about this error, try `rustc --explain E0004`.

0 commit comments

Comments
 (0)
Please sign in to comment.