match in closure: capture non_exhaustive even if defined in current crate

This commit is contained in:
Maja Kądziołka 2026-01-04 04:22:55 +01:00
parent 3c175080dc
commit ee1a6f4e88
7 changed files with 39 additions and 86 deletions

View file

@ -818,14 +818,12 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
/// The core driver for walking a pattern
///
/// This should mirror how pattern-matching gets lowered to MIR, as
/// otherwise lowering will ICE when trying to resolve the upvars.
/// otherwise said lowering will ICE when trying to resolve the upvars.
///
/// However, it is okay to approximate it here by doing *more* accesses than
/// the actual MIR builder will, which is useful when some checks are too
/// cumbersome to perform here. For example, if after typeck it becomes
/// clear that only one variant of an enum is inhabited, and therefore a
/// read of the discriminant is not necessary, `walk_pat` will have
/// over-approximated the necessary upvar capture granularity.
/// cumbersome to perform here, because e.g. they require more typeck results
/// than available.
///
/// Do note that discrepancies like these do still create obscure corners
/// in the semantics of the language, and should be avoided if possible.
@ -1852,26 +1850,13 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
}
/// Checks whether a type has multiple variants, and therefore, whether a
/// read of the discriminant might be necessary. Note that the actual MIR
/// builder code does a more specific check, filtering out variants that
/// happen to be uninhabited.
///
/// Here, it is not practical to perform such a check, because inhabitedness
/// queries require typeck results, and typeck requires closure capture analysis.
///
/// Moreover, the language is moving towards uninhabited variants still semantically
/// causing a discriminant read, so we *shouldn't* perform any such check.
///
/// FIXME(never_patterns): update this comment once the aforementioned MIR builder
/// code is changed to be insensitive to inhhabitedness.
/// read of the discriminant might be necessary.
#[instrument(skip(self, span), level = "debug")]
fn is_multivariant_adt(&self, ty: Ty<'tcx>, span: Span) -> bool {
if let ty::Adt(def, _) = self.cx.structurally_resolve_type(span, ty).kind() {
// Note that if a non-exhaustive SingleVariant is defined in another crate, we need
// to assume that more cases will be added to the variant in the future. This mean
// that we should handle non-exhaustive SingleVariant the same way we would handle
// a MultiVariant.
def.variants().len() > 1 || def.variant_list_has_applicable_non_exhaustive()
// We treat non-exhaustive enums the same independent of the crate they are
// defined in, to avoid differences in the operational semantics between crates.
def.variants().len() > 1 || def.is_variant_list_non_exhaustive()
} else {
false
}

View file

@ -28,12 +28,6 @@ fn main() {
let _b = || { match l1 { L1::A => () } };
//~^ ERROR: non-exhaustive patterns: `L1::B` not covered [E0004]
// l2 should not be captured as it is a non-exhaustive SingleVariant
// defined in this crate
let _c = || { match l2 { L2::C => (), _ => () } };
let mut mut_l2 = l2;
_c();
// E1 is not visibly uninhabited from here
let (e1, e2, e3, e4) = bar();
let _d = || { match e1 {} };
@ -42,8 +36,14 @@ fn main() {
//~^ ERROR: non-exhaustive patterns: `_` not covered [E0004]
let _f = || { match e2 { E2::A => (), E2::B => (), _ => () } };
// e3 should be captured as it is a non-exhaustive SingleVariant
// defined in another crate
// non-exhaustive enums should always be captured, regardless if they
// are defined in the current crate:
let _c = || { match l2 { L2::C => (), _ => () } };
let mut mut_l2 = l2;
//~^ ERROR: cannot move out of `l2` because it is borrowed
_c();
// ...or in another crate:
let _g = || { match e3 { E3::C => (), _ => () } };
let mut mut_e3 = e3;
//~^ ERROR: cannot move out of `e3` because it is borrowed

View file

@ -16,7 +16,7 @@ LL | let _b = || { match l1 { L1::A => (), L1::B => todo!() } };
| ++++++++++++++++++
error[E0004]: non-exhaustive patterns: type `E1` is non-empty
--> $DIR/non-exhaustive-match.rs:39:25
--> $DIR/non-exhaustive-match.rs:33:25
|
LL | let _d = || { match e1 {} };
| ^^
@ -35,7 +35,7 @@ LL ~ } };
|
error[E0004]: non-exhaustive patterns: `_` not covered
--> $DIR/non-exhaustive-match.rs:41:25
--> $DIR/non-exhaustive-match.rs:35:25
|
LL | let _e = || { match e2 { E2::A => (), E2::B => () } };
| ^^ pattern `_` not covered
@ -52,6 +52,19 @@ help: ensure that all possible cases are being handled by adding a match arm wit
LL | let _e = || { match e2 { E2::A => (), E2::B => (), _ => todo!() } };
| ++++++++++++++
error[E0505]: cannot move out of `l2` because it is borrowed
--> $DIR/non-exhaustive-match.rs:42:22
|
LL | let _c = || { match l2 { L2::C => (), _ => () } };
| -- -- borrow occurs due to use in closure
| |
| borrow of `l2` occurs here
LL | let mut mut_l2 = l2;
| ^^ move out of `l2` occurs here
LL |
LL | _c();
| -- borrow later used here
error[E0505]: cannot move out of `e3` because it is borrowed
--> $DIR/non-exhaustive-match.rs:48:22
|
@ -65,7 +78,7 @@ LL |
LL | _g();
| -- borrow later used here
error: aborting due to 4 previous errors
error: aborting due to 5 previous errors
Some errors have detailed explanations: E0004, E0505.
For more information about an error, try `rustc --explain E0004`.

View file

@ -1,3 +1,8 @@
// Make sure that #[non_exhaustive] cannot cause drop order to depend on which
// crate the code is in.
//
// See rust-lang/rust#147722
//
//@ edition:2021
//@ run-pass
//@ check-run-results

View file

@ -12,8 +12,8 @@ dropping b
non exhaustive:
before assign
dropping a
after assign
dropping a
dropping b
external non exhaustive:

View file

@ -78,10 +78,9 @@ pub fn test_two_variants(x: TwoVariants) -> impl FnOnce() {
}
}
// ...and single-variant, non-exhaustive enums *should* behave as if they had multiple variants
// ...and single-variant, non-exhaustive enums behave as if they had multiple variants
pub fn test_non_exhaustive1(x: NonExhaustive) -> impl FnOnce() {
|| {
//~^ ERROR: closure may outlive the current function, but it borrows `x.0`
match x {
NonExhaustive::A(a, b) => {
drop((a, b));
@ -94,7 +93,6 @@ pub fn test_non_exhaustive1(x: NonExhaustive) -> impl FnOnce() {
// (again, wildcard branch or not)
pub fn test_non_exhaustive2(x: NonExhaustive) -> impl FnOnce() {
|| {
//~^ ERROR: closure may outlive the current function, but it borrows `x.0`
match x {
NonExhaustive::A(a, b) => {
drop((a, b));

View file

@ -70,54 +70,6 @@ help: to force the closure to take ownership of `x.0` (and any other referenced
LL | move || {
| ++++
error[E0373]: closure may outlive the current function, but it borrows `x.0`, which is owned by the current function
--> $DIR/partial-move.rs:83:5
|
LL | || {
| ^^ may outlive borrowed value `x.0`
LL |
LL | match x {
| - `x.0` is borrowed here
|
note: closure is returned here
--> $DIR/partial-move.rs:83:5
|
LL | / || {
LL | |
LL | | match x {
LL | | NonExhaustive::A(a, b) => {
... |
LL | | }
| |_____^
help: to force the closure to take ownership of `x.0` (and any other referenced variables), use the `move` keyword
|
LL | move || {
| ++++
error[E0373]: closure may outlive the current function, but it borrows `x.0`, which is owned by the current function
--> $DIR/partial-move.rs:96:5
|
LL | || {
| ^^ may outlive borrowed value `x.0`
LL |
LL | match x {
| - `x.0` is borrowed here
|
note: closure is returned here
--> $DIR/partial-move.rs:96:5
|
LL | / || {
LL | |
LL | | match x {
LL | | NonExhaustive::A(a, b) => {
... |
LL | | }
| |_____^
help: to force the closure to take ownership of `x.0` (and any other referenced variables), use the `move` keyword
|
LL | move || {
| ++++
error: aborting due to 5 previous errors
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0373`.