Skip to content

Commit

Permalink
Auto merge of #88280 - sexxi-goose:non-exhaustive, r=nikomatsakis
Browse files Browse the repository at this point in the history
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 rust-lang/project-rfc-2229#59
r? `@nikomatsakis`
  • Loading branch information
bors committed Aug 29, 2021
2 parents ae0b03b + 33817d2 commit 2f662b1
Show file tree
Hide file tree
Showing 17 changed files with 185 additions and 2 deletions.
19 changes: 17 additions & 2 deletions compiler/rustc_typeck/src/expr_use_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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 }
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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`.
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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`.

0 comments on commit 2f662b1

Please sign in to comment.