Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

exhaustiveness: Rework constructor splitting #116391

Merged
merged 8 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Fix handling slices of empty types
  • Loading branch information
Nadrieril committed Oct 4, 2023
commit c1b29b338dc37f96f0695e393743ce35508d3704
29 changes: 21 additions & 8 deletions compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,7 @@ impl<'tcx> Constructor<'tcx> {
}

/// Describes the set of all constructors for a type.
#[derive(Debug)]
pub(super) enum ConstructorSet {
/// The type has a single constructor, e.g. `&T` or a struct.
Single,
Expand Down Expand Up @@ -903,6 +904,7 @@ pub(super) enum ConstructorSet {
/// - constructors in `present` and `missing` are split for the column; in other words, they are
/// either fully included in or disjoint from each constructor in the column. This avoids
/// non-trivial intersections like between `0..10` and `5..15`.
#[derive(Debug)]
struct SplitConstructorSet<'tcx> {
present: SmallVec<[Constructor<'tcx>; 1]>,
missing: Vec<Constructor<'tcx>>,
Expand All @@ -911,8 +913,8 @@ struct SplitConstructorSet<'tcx> {
}

impl ConstructorSet {
#[instrument(level = "debug", skip(cx), ret)]
pub(super) fn for_ty<'p, 'tcx>(cx: &MatchCheckCtxt<'p, 'tcx>, ty: Ty<'tcx>) -> Self {
debug!("ConstructorSet::for_ty({:?})", ty);
let make_range =
|start, end| IntRange::from_range(cx.tcx, start, end, ty, RangeEnd::Included);
// This determines the set of all possible constructors for the type `ty`. For numbers,
Expand Down Expand Up @@ -1036,6 +1038,7 @@ impl ConstructorSet {
/// This is the core logical operation of exhaustiveness checking. This analyzes a column a
/// constructors to 1/ determine which constructors of the type (if any) are missing; 2/ split
/// constructors to handle non-trivial intersections e.g. on ranges or slices.
#[instrument(level = "debug", skip(self, pcx, ctors), ret)]
fn split<'a, 'tcx>(
&self,
pcx: &PatCtxt<'_, '_, 'tcx>,
Expand Down Expand Up @@ -1111,7 +1114,7 @@ impl ConstructorSet {
}
&ConstructorSet::Slice(array_len) => {
let seen_slices = seen.map(|c| c.as_slice().unwrap());
let base_slice = Slice { kind: VarLen(0, 0), array_len };
let base_slice = Slice::new(array_len, VarLen(0, 0));
for (seen, splitted_slice) in base_slice.split(seen_slices) {
let ctor = Slice(splitted_slice);
match seen {
Expand All @@ -1121,12 +1124,22 @@ impl ConstructorSet {
}
}
ConstructorSet::SliceOfEmpty => {
// Behaves essentially like `Single`.
let slice = Slice(Slice::new(None, FixedLen(0)));
if seen.next().is_none() {
missing.push(slice);
} else {
present.push(slice);
// This one is tricky because even though there's only one possible value of this
// type (namely `[]`), slice patterns of all lengths are allowed, they're just
// unreachable if length != 0.
// We still gather the seen constructors in `present`, but the only slice that can
// go in `missing` is `[]`.
let seen_slices = seen.map(|c| c.as_slice().unwrap());
let base_slice = Slice::new(None, VarLen(0, 0));
for (seen, splitted_slice) in base_slice.split(seen_slices) {
let ctor = Slice(splitted_slice);
match seen {
Presence::Seen => present.push(ctor),
Presence::Unseen if splitted_slice.arity() == 0 => {
missing.push(Slice(Slice::new(None, FixedLen(0))))
}
Presence::Unseen => {}
}
}
}
ConstructorSet::Unlistable => {
Expand Down
22 changes: 22 additions & 0 deletions tests/ui/pattern/usefulness/slice_of_empty.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 39 additions & 0 deletions tests/ui/pattern/usefulness/slice_of_empty.stderr
Original file line number Diff line number Diff line change
@@ -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`.