Auto merge of #88280 - sexxi-goose:non-exhaustive, r=nikomatsakis

Handle match statements with non exhaustive variants in closures

This PR ensures that the behavior for match statements with non exhaustive variants is the same inside and outside closures.

If we have a non-exhaustive SingleVariant which is defined in a different crate, then we should handle the case the same way we would handle a MultiVariant: borrow the match discriminant.

Closes https://github.com/rust-lang/project-rfc-2229/issues/59
r? `@nikomatsakis`
This commit is contained in:
bors 2021-08-29 20:27:14 +00:00
commit 2f662b1403
17 changed files with 185 additions and 2 deletions

View file

@ -15,7 +15,7 @@ use rustc_index::vec::Idx;
use rustc_infer::infer::InferCtxt;
use rustc_middle::hir::place::ProjectionKind;
use rustc_middle::mir::FakeReadCause;
use rustc_middle::ty::{self, adjustment, Ty, TyCtxt};
use rustc_middle::ty::{self, adjustment, AdtKind, Ty, TyCtxt};
use rustc_target::abi::VariantIdx;
use std::iter;
@ -845,5 +845,20 @@ fn delegate_consume<'a, 'tcx>(
}
fn is_multivariant_adt(ty: Ty<'tcx>) -> bool {
if let ty::Adt(def, _) = ty.kind() { def.variants.len() > 1 } else { false }
if let ty::Adt(def, _) = 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.
// If the variant is not local it must be defined in another crate.
let is_non_exhaustive = match def.adt_kind() {
AdtKind::Struct | AdtKind::Union => {
def.non_enum_variant().is_field_list_non_exhaustive()
}
AdtKind::Enum => def.is_variant_list_non_exhaustive(),
};
def.variants.len() > 1 || (!def.did.is_local() && is_non_exhaustive)
} else {
false
}
}

View file

@ -0,0 +1,10 @@
#[non_exhaustive]
pub enum E1 {}
#[non_exhaustive]
pub enum E2 { A, B }
#[non_exhaustive]
pub enum E3 { C }
pub enum E4 { D }

View file

@ -0,0 +1,37 @@
// edition:2021
enum SingleVariant {
A
}
struct TestStruct {
x: i32,
y: i32,
z: i32,
}
fn edge_case_if() {
let sv = SingleVariant::A;
let condition = true;
// sv should not be captured as it is a SingleVariant
let _a = || {
match sv {
SingleVariant::A if condition => (),
_ => ()
}
};
let mut mut_sv = sv;
_a();
// ts should be captured
let ts = TestStruct { x: 1, y: 1, z: 1 };
let _b = || { match ts {
TestStruct{ x: 1, .. } => (),
_ => ()
}};
let mut mut_ts = ts;
//~^ ERROR: cannot move out of `ts` because it is borrowed
_b();
}
fn main() {}

View file

@ -0,0 +1,17 @@
error[E0505]: cannot move out of `ts` because it is borrowed
--> $DIR/match-edge-cases_2.rs:32:22
|
LL | let _b = || { match ts {
| -- -- borrow occurs due to use in closure
| |
| borrow of `ts` occurs here
...
LL | let mut mut_ts = ts;
| ^^ move out of `ts` occurs here
LL |
LL | _b();
| -- borrow later used here
error: aborting due to previous error
For more information about this error, try `rustc --explain E0505`.

View file

@ -0,0 +1,54 @@
// edition:2021
// aux-build:match_non_exhaustive_lib.rs
/* The error message for non-exhaustive matches on non-local enums
* marked as non-exhaustive should mention the fact that the enum
* is marked as non-exhaustive (issue #85227).
*/
// Ignore non_exhaustive in the same crate
#[non_exhaustive]
enum L1 { A, B }
enum L2 { C }
extern crate match_non_exhaustive_lib;
use match_non_exhaustive_lib::{E1, E2, E3, E4};
fn foo() -> (L1, L2) {todo!()}
fn bar() -> (E1, E2, E3, E4) {todo!()}
fn main() {
let (l1, l2) = foo();
// No error for enums defined in this crate
let _a = || { match l1 { L1::A => (), L1::B => () } };
// (except if the match is already non-exhaustive)
let _b = || { match l1 { L1::A => () } };
//~^ ERROR: non-exhaustive patterns: `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 {} };
//~^ ERROR: non-exhaustive patterns: type `E1` is non-empty [E0004]
let _e = || { match e2 { E2::A => (), E2::B => () } };
//~^ 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
let _g = || { match e3 { E3::C => (), _ => () } };
let mut mut_e3 = e3;
//~^ ERROR: cannot move out of `e3` because it is borrowed
_g();
// e4 should not be captured as it is a SingleVariant
let _h = || { match e4 { E4::D => (), _ => () } };
let mut mut_e4 = e4;
_h();
}

View file

@ -0,0 +1,50 @@
error[E0004]: non-exhaustive patterns: `B` not covered
--> $DIR/non-exhaustive-match.rs:26:25
|
LL | enum L1 { A, B }
| ----------------
| | |
| | not covered
| `L1` defined here
...
LL | let _b = || { match l1 { L1::A => () } };
| ^^ pattern `B` not covered
|
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
= note: the matched value is of type `L1`
error[E0004]: non-exhaustive patterns: type `E1` is non-empty
--> $DIR/non-exhaustive-match.rs:37:25
|
LL | let _d = || { match e1 {} };
| ^^
|
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
= note: the matched value is of type `E1`, which is marked as non-exhaustive
error[E0004]: non-exhaustive patterns: `_` not covered
--> $DIR/non-exhaustive-match.rs:39:25
|
LL | let _e = || { match e2 { E2::A => (), E2::B => () } };
| ^^ pattern `_` not covered
|
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
= note: the matched value is of type `E2`, which is marked as non-exhaustive
error[E0505]: cannot move out of `e3` because it is borrowed
--> $DIR/non-exhaustive-match.rs:46:22
|
LL | let _g = || { match e3 { E3::C => (), _ => () } };
| -- -- borrow occurs due to use in closure
| |
| borrow of `e3` occurs here
LL | let mut mut_e3 = e3;
| ^^ move out of `e3` occurs here
LL |
LL | _g();
| -- borrow later used here
error: aborting due to 4 previous errors
Some errors have detailed explanations: E0004, E0505.
For more information about an error, try `rustc --explain E0004`.