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:
commit
e20cb77021
4 changed files with 734 additions and 589 deletions
File diff suppressed because it is too large
Load diff
|
@ -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.
|
||||||
|
|
22
tests/ui/pattern/usefulness/slice_of_empty.rs
Normal file
22
tests/ui/pattern/usefulness/slice_of_empty.rs
Normal 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
|
||||||
|
};
|
||||||
|
}
|
39
tests/ui/pattern/usefulness/slice_of_empty.stderr
Normal file
39
tests/ui/pattern/usefulness/slice_of_empty.stderr
Normal 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`.
|
Loading…
Add table
Add a link
Reference in a new issue