1
Fork 0

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](https://github.com/rust-lang/rust/pull/76918)), my one-pass rewrite (https://github.com/rust-lang/rust/pull/116042), and future librarification.
This commit is contained in:
bors 2023-10-12 21:33:31 +00:00
commit e20cb77021
4 changed files with 734 additions and 589 deletions

File diff suppressed because it is too large Load diff

View file

@ -307,7 +307,7 @@
use self::ArmType::*; use self::ArmType::*;
use self::Usefulness::*; use self::Usefulness::*;
use super::deconstruct_pat::{Constructor, DeconstructedPat, Fields, SplitWildcard}; use super::deconstruct_pat::{Constructor, ConstructorSet, DeconstructedPat, Fields};
use crate::errors::{NonExhaustiveOmittedPattern, Uncovered}; use crate::errors::{NonExhaustiveOmittedPattern, Uncovered};
use rustc_data_structures::captures::Captures; use rustc_data_structures::captures::Captures;
@ -368,8 +368,6 @@ pub(super) struct PatCtxt<'a, 'p, 'tcx> {
/// Whether the current pattern is the whole pattern as found in a match arm, or if it's a /// Whether the current pattern is the whole pattern as found in a match arm, or if it's a
/// subpattern. /// subpattern.
pub(super) is_top_level: bool, pub(super) is_top_level: bool,
/// Whether the current pattern is from a `non_exhaustive` enum.
pub(super) is_non_exhaustive: bool,
} }
impl<'a, 'p, 'tcx> fmt::Debug for PatCtxt<'a, 'p, 'tcx> { impl<'a, 'p, 'tcx> fmt::Debug for PatCtxt<'a, 'p, 'tcx> {
@ -616,62 +614,41 @@ impl<'p, 'tcx> Usefulness<'p, 'tcx> {
WithWitnesses(ref witnesses) if witnesses.is_empty() => self, WithWitnesses(ref witnesses) if witnesses.is_empty() => self,
WithWitnesses(witnesses) => { WithWitnesses(witnesses) => {
let new_witnesses = if let Constructor::Missing { .. } = ctor { let new_witnesses = if let Constructor::Missing { .. } = ctor {
// We got the special `Missing` constructor, so each of the missing constructors let mut missing = ConstructorSet::for_ty(pcx.cx, pcx.ty)
// gives a new pattern that is not caught by the match. We list those patterns. .compute_missing(pcx, matrix.heads().map(DeconstructedPat::ctor));
if pcx.is_non_exhaustive { if missing.iter().any(|c| c.is_non_exhaustive()) {
witnesses // We only report `_` here; listing other constructors would be redundant.
.into_iter() missing = vec![Constructor::NonExhaustive];
// Here we don't want the user to try to list all variants, we want them to add
// a wildcard, so we only suggest that.
.map(|witness| {
witness.apply_constructor(pcx, &Constructor::NonExhaustive)
})
.collect()
} else {
let mut split_wildcard = SplitWildcard::new(pcx);
split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor));
// This lets us know if we skipped any variants because they are marked
// `doc(hidden)` or they are unstable feature gate (only stdlib types).
let mut hide_variant_show_wild = false;
// Construct for each missing constructor a "wild" version of this
// constructor, that matches everything that can be built with
// it. For example, if `ctor` is a `Constructor::Variant` for
// `Option::Some`, we get the pattern `Some(_)`.
let mut new_patterns: Vec<DeconstructedPat<'_, '_>> = split_wildcard
.iter_missing(pcx)
.filter_map(|missing_ctor| {
// Check if this variant is marked `doc(hidden)`
if missing_ctor.is_doc_hidden_variant(pcx)
|| missing_ctor.is_unstable_variant(pcx)
{
hide_variant_show_wild = true;
return None;
}
Some(DeconstructedPat::wild_from_ctor(pcx, missing_ctor.clone()))
})
.collect();
if hide_variant_show_wild {
new_patterns.push(DeconstructedPat::wildcard(pcx.ty, pcx.span));
}
witnesses
.into_iter()
.flat_map(|witness| {
new_patterns.iter().map(move |pat| {
Witness(
witness
.0
.iter()
.chain(once(pat))
.map(DeconstructedPat::clone_and_forget_reachability)
.collect(),
)
})
})
.collect()
} }
// We got the special `Missing` constructor, so each of the missing constructors
// gives a new pattern that is not caught by the match.
// We construct for each missing constructor a version of this constructor with
// wildcards for fields, i.e. that matches everything that can be built with it.
// For example, if `ctor` is a `Constructor::Variant` for `Option::Some`, we get
// the pattern `Some(_)`.
let new_patterns: Vec<DeconstructedPat<'_, '_>> = missing
.into_iter()
.map(|missing_ctor| {
DeconstructedPat::wild_from_ctor(pcx, missing_ctor.clone())
})
.collect();
witnesses
.into_iter()
.flat_map(|witness| {
new_patterns.iter().map(move |pat| {
Witness(
witness
.0
.iter()
.chain(once(pat))
.map(DeconstructedPat::clone_and_forget_reachability)
.collect(),
)
})
})
.collect()
} else { } else {
witnesses witnesses
.into_iter() .into_iter()
@ -844,9 +821,8 @@ fn is_useful<'p, 'tcx>(
ty = row.head().ty(); ty = row.head().ty();
} }
} }
let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty);
debug!("v.head: {:?}, v.span: {:?}", v.head(), v.head().span()); debug!("v.head: {:?}, v.span: {:?}", v.head(), v.head().span());
let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level, is_non_exhaustive }; let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level };
let v_ctor = v.head().ctor(); let v_ctor = v.head().ctor();
debug!(?v_ctor); debug!(?v_ctor);
@ -861,7 +837,8 @@ fn is_useful<'p, 'tcx>(
} }
// We split the head constructor of `v`. // We split the head constructor of `v`.
let split_ctors = v_ctor.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); let split_ctors = v_ctor.split(pcx, matrix.heads().map(DeconstructedPat::ctor));
let is_non_exhaustive_and_wild = is_non_exhaustive && v_ctor.is_wildcard(); let is_non_exhaustive_and_wild =
cx.is_foreign_non_exhaustive_enum(ty) && v_ctor.is_wildcard();
// For each constructor, we compute whether there's a value that starts with it that would // For each constructor, we compute whether there's a value that starts with it that would
// witness the usefulness of `v`. // witness the usefulness of `v`.
let start_matrix = &matrix; let start_matrix = &matrix;
@ -895,27 +872,21 @@ fn is_useful<'p, 'tcx>(
&& usefulness.is_useful() && matches!(witness_preference, RealArm) && usefulness.is_useful() && matches!(witness_preference, RealArm)
&& matches!( && matches!(
&ctor, &ctor,
Constructor::Missing { nonexhaustive_enum_missing_real_variants: true } Constructor::Missing { nonexhaustive_enum_missing_visible_variants: true }
) )
{ {
let patterns = { let missing = ConstructorSet::for_ty(pcx.cx, pcx.ty)
let mut split_wildcard = SplitWildcard::new(pcx); .compute_missing(pcx, matrix.heads().map(DeconstructedPat::ctor));
split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); // Construct for each missing constructor a "wild" version of this constructor, that
// Construct for each missing constructor a "wild" version of this // matches everything that can be built with it. For example, if `ctor` is a
// constructor, that matches everything that can be built with // `Constructor::Variant` for `Option::Some`, we get the pattern `Some(_)`.
// it. For example, if `ctor` is a `Constructor::Variant` for let patterns = missing
// `Option::Some`, we get the pattern `Some(_)`. .into_iter()
split_wildcard // Because of how we computed `nonexhaustive_enum_missing_visible_variants`,
.iter_missing(pcx) // this will not return an empty `Vec`.
// Filter out the `NonExhaustive` because we want to list only real .filter(|c| !(matches!(c, Constructor::NonExhaustive | Constructor::Hidden)))
// variants. Also remove any unstable feature gated variants. .map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor))
// Because of how we computed `nonexhaustive_enum_missing_real_variants`, .collect::<Vec<_>>();
// this will not return an empty `Vec`.
.filter(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx)))
.cloned()
.map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor))
.collect::<Vec<_>>()
};
// Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns` // Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns`
// is not exhaustive enough. // is not exhaustive enough.

View file

@ -0,0 +1,22 @@
#![feature(never_type)]
#![feature(exhaustive_patterns)]
#![deny(unreachable_patterns)]
fn main() {}
fn foo(nevers: &[!]) {
match nevers {
&[] => (),
};
match nevers {
&[] => (),
&[_] => (), //~ ERROR unreachable pattern
&[_, _, ..] => (), //~ ERROR unreachable pattern
};
match nevers {
//~^ ERROR non-exhaustive patterns: `&[]` not covered
&[_] => (), //~ ERROR unreachable pattern
};
}

View file

@ -0,0 +1,39 @@
error: unreachable pattern
--> $DIR/slice_of_empty.rs:14:9
|
LL | &[_] => (),
| ^^^^
|
note: the lint level is defined here
--> $DIR/slice_of_empty.rs:3:9
|
LL | #![deny(unreachable_patterns)]
| ^^^^^^^^^^^^^^^^^^^^
error: unreachable pattern
--> $DIR/slice_of_empty.rs:15:9
|
LL | &[_, _, ..] => (),
| ^^^^^^^^^^^
error: unreachable pattern
--> $DIR/slice_of_empty.rs:20:9
|
LL | &[_] => (),
| ^^^^
error[E0004]: non-exhaustive patterns: `&[]` not covered
--> $DIR/slice_of_empty.rs:18:11
|
LL | match nevers {
| ^^^^^^ pattern `&[]` not covered
|
= note: the matched value is of type `&[!]`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
LL | &[_] => (), &[] => todo!(),
| ++++++++++++++++
error: aborting due to 4 previous errors
For more information about this error, try `rustc --explain E0004`.