Skip to content

remove special-casing of boxes from match exhaustiveness/usefulness analysis #143414

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

Merged
merged 1 commit into from
Jul 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 4 additions & 3 deletions compiler/rustc_mir_build/src/thir/pattern/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,10 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
}
PatKind::Deref { subpattern }
}
hir::PatKind::Box(subpattern) => {
PatKind::Deref { subpattern: self.lower_pattern(subpattern) }
}
hir::PatKind::Box(subpattern) => PatKind::DerefPattern {
subpattern: self.lower_pattern(subpattern),
borrow: hir::ByRef::No,
},

hir::PatKind::Slice(prefix, slice, suffix) => {
self.slice_or_array_pattern(pat.span, ty, prefix, slice, suffix)
Expand Down
87 changes: 24 additions & 63 deletions compiler/rustc_pattern_analysis/src/rustc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,27 +221,19 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
let slice = match ctor {
Struct | Variant(_) | UnionField => match ty.kind() {
ty::Tuple(fs) => reveal_and_alloc(cx, fs.iter()),
ty::Adt(adt, args) => {
if adt.is_box() {
// The only legal patterns of type `Box` (outside `std`) are `_` and box
// patterns. If we're here we can assume this is a box pattern.
reveal_and_alloc(cx, once(args.type_at(0)))
} else {
let variant =
&adt.variant(RustcPatCtxt::variant_index_for_adt(&ctor, *adt));
let tys = cx.variant_sub_tys(ty, variant).map(|(field, ty)| {
let is_visible =
adt.is_enum() || field.vis.is_accessible_from(cx.module, cx.tcx);
let is_uninhabited = cx.is_uninhabited(*ty);
let is_unstable =
cx.tcx.lookup_stability(field.did).is_some_and(|stab| {
stab.is_unstable() && stab.feature != sym::rustc_private
});
let skip = is_uninhabited && (!is_visible || is_unstable);
(ty, PrivateUninhabitedField(skip))
ty::Adt(adt, _) => {
let variant = &adt.variant(RustcPatCtxt::variant_index_for_adt(&ctor, *adt));
let tys = cx.variant_sub_tys(ty, variant).map(|(field, ty)| {
let is_visible =
adt.is_enum() || field.vis.is_accessible_from(cx.module, cx.tcx);
let is_uninhabited = cx.is_uninhabited(*ty);
let is_unstable = cx.tcx.lookup_stability(field.did).is_some_and(|stab| {
stab.is_unstable() && stab.feature != sym::rustc_private
});
cx.dropless_arena.alloc_from_iter(tys)
}
let skip = is_uninhabited && (!is_visible || is_unstable);
(ty, PrivateUninhabitedField(skip))
});
cx.dropless_arena.alloc_from_iter(tys)
}
_ => bug!("Unexpected type for constructor `{ctor:?}`: {ty:?}"),
},
Expand Down Expand Up @@ -273,14 +265,8 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
Struct | Variant(_) | UnionField => match ty.kind() {
ty::Tuple(fs) => fs.len(),
ty::Adt(adt, ..) => {
if adt.is_box() {
// The only legal patterns of type `Box` (outside `std`) are `_` and box
// patterns. If we're here we can assume this is a box pattern.
1
} else {
let variant_idx = RustcPatCtxt::variant_index_for_adt(&ctor, *adt);
adt.variant(variant_idx).fields.len()
}
let variant_idx = RustcPatCtxt::variant_index_for_adt(&ctor, *adt);
adt.variant(variant_idx).fields.len()
}
_ => bug!("Unexpected type for constructor `{ctor:?}`: {ty:?}"),
},
Expand Down Expand Up @@ -470,8 +456,6 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
fields = vec![self.lower_pat(subpattern).at_index(0)];
arity = 1;
ctor = match ty.kind() {
// This is a box pattern.
ty::Adt(adt, ..) if adt.is_box() => Struct,
ty::Ref(..) => Ref,
_ => span_bug!(
pat.span,
Expand Down Expand Up @@ -501,28 +485,6 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
.map(|ipat| self.lower_pat(&ipat.pattern).at_index(ipat.field.index()))
.collect();
}
ty::Adt(adt, _) if adt.is_box() => {
// The only legal patterns of type `Box` (outside `std`) are `_` and box
// patterns. If we're here we can assume this is a box pattern.
// FIXME(Nadrieril): A `Box` can in theory be matched either with `Box(_,
// _)` or a box pattern. As a hack to avoid an ICE with the former, we
// ignore other fields than the first one. This will trigger an error later
// anyway.
// See https://github.com/rust-lang/rust/issues/82772,
// explanation: https://github.com/rust-lang/rust/pull/82789#issuecomment-796921977
// The problem is that we can't know from the type whether we'll match
// normally or through box-patterns. We'll have to figure out a proper
// solution when we introduce generalized deref patterns. Also need to
// prevent mixing of those two options.
let pattern = subpatterns.into_iter().find(|pat| pat.field.index() == 0);
if let Some(pat) = pattern {
fields = vec![self.lower_pat(&pat.pattern).at_index(0)];
} else {
fields = vec![];
}
ctor = Struct;
arity = 1;
}
ty::Adt(adt, _) => {
ctor = match pat.kind {
PatKind::Leaf { .. } if adt.is_union() => UnionField,
Expand Down Expand Up @@ -825,11 +787,6 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
Bool(b) => b.to_string(),
Str(s) => s.to_string(),
IntRange(range) => return self.print_pat_range(range, *pat.ty()),
Struct if pat.ty().is_box() => {
// Outside of the `alloc` crate, the only way to create a struct pattern
// of type `Box` is to use a `box` pattern via #[feature(box_patterns)].
format!("box {}", print(&pat.fields[0]))
}
Struct | Variant(_) | UnionField => {
let enum_info = match *pat.ty().kind() {
ty::Adt(adt_def, _) if adt_def.is_enum() => EnumInfo::Enum {
Expand Down Expand Up @@ -866,6 +823,14 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
print::write_ref_like(&mut s, pat.ty().inner(), &print(&pat.fields[0])).unwrap();
s
}
DerefPattern(_) if pat.ty().is_box() && !self.tcx.features().deref_patterns() => {
// FIXME(deref_patterns): Remove this special handling once `box_patterns` is gone.
// HACK(@dianne): `box _` syntax is exposed on stable in diagnostics, e.g. to
// witness non-exhaustiveness of `match Box::new(0) { Box { .. } if false => {} }`.
// To avoid changing diagnostics before deref pattern syntax is finalized, let's use
// `box _` syntax unless `deref_patterns` is enabled.
format!("box {}", print(&pat.fields[0]))
}
DerefPattern(_) => format!("deref!({})", print(&pat.fields[0])),
Slice(slice) => {
let (prefix_len, has_dot_dot) = match slice.kind {
Expand Down Expand Up @@ -964,12 +929,8 @@ impl<'p, 'tcx: 'p> PatCx for RustcPatCtxt<'p, 'tcx> {
ty: &Self::Ty,
) -> fmt::Result {
if let ty::Adt(adt, _) = ty.kind() {
if adt.is_box() {
write!(f, "Box")?
} else {
let variant = adt.variant(Self::variant_index_for_adt(ctor, *adt));
write!(f, "{}", variant.name)?;
}
let variant = adt.variant(Self::variant_index_for_adt(ctor, *adt));
write!(f, "{}", variant.name)?;
}
Ok(())
}
Expand Down
6 changes: 5 additions & 1 deletion tests/ui/uninhabited/uninhabited-patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ fn main() {

let x: Result<Box<NotSoSecretlyEmpty>, &[Result<!, !>]> = Err(&[]);
match x {
Ok(box _) => (), //~ ERROR unreachable pattern
Ok(box _) => (), // We'd get a non-exhaustiveness error if this arm was removed; don't lint.
Err(&[]) => (),
Err(&[..]) => (),
}
match x { //~ ERROR non-exhaustive patterns
Err(&[]) => (),
Err(&[..]) => (),
}
Expand Down
36 changes: 22 additions & 14 deletions tests/ui/uninhabited/uninhabited-patterns.stderr
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
error: unreachable pattern
--> $DIR/uninhabited-patterns.rs:30:9
error[E0004]: non-exhaustive patterns: `Ok(_)` not covered
--> $DIR/uninhabited-patterns.rs:34:11
|
LL | Ok(box _) => (),
| ^^^^^^^^^-------
| |
| matches no values because `NotSoSecretlyEmpty` is uninhabited
| help: remove the match arm
LL | match x {
| ^ pattern `Ok(_)` not covered
|
= note: to learn more about uninhabited types, see https://doc.rust-lang.org/nomicon/exotic-sizes.html#empty-types
note: the lint level is defined here
--> $DIR/uninhabited-patterns.rs:4:9
note: `Result<Box<NotSoSecretlyEmpty>, &[Result<!, !>]>` defined here
--> $SRC_DIR/core/src/result.rs:LL:COL
::: $SRC_DIR/core/src/result.rs:LL:COL
|
= note: not covered
= note: the matched value is of type `Result<Box<NotSoSecretlyEmpty>, &[Result<!, !>]>`
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 ~ Err(&[..]) => (),
LL ~ Ok(_) => todo!(),
|
LL | #![deny(unreachable_patterns)]
| ^^^^^^^^^^^^^^^^^^^^

error: unreachable pattern
--> $DIR/uninhabited-patterns.rs:39:9
--> $DIR/uninhabited-patterns.rs:43:9
|
LL | Err(Ok(_y)) => (),
| ^^^^^^^^^^^-------
Expand All @@ -24,9 +26,14 @@ LL | Err(Ok(_y)) => (),
| help: remove the match arm
|
= note: to learn more about uninhabited types, see https://doc.rust-lang.org/nomicon/exotic-sizes.html#empty-types
note: the lint level is defined here
--> $DIR/uninhabited-patterns.rs:4:9
|
LL | #![deny(unreachable_patterns)]
| ^^^^^^^^^^^^^^^^^^^^

error: unreachable pattern
--> $DIR/uninhabited-patterns.rs:42:15
--> $DIR/uninhabited-patterns.rs:46:15
|
LL | while let Some(_y) = foo() {
| ^^^^^^^^ matches no values because `NotSoSecretlyEmpty` is uninhabited
Expand All @@ -35,3 +42,4 @@ LL | while let Some(_y) = foo() {

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0004`.
Loading