Skip to content

Commit 2f662b1

Browse files
committed
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 rust-lang/project-rfc-2229#59 r? `@nikomatsakis`
2 parents ae0b03b + 33817d2 commit 2f662b1

17 files changed

+185
-2
lines changed

compiler/rustc_typeck/src/expr_use_visitor.rs

+17-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use rustc_index::vec::Idx;
1515
use rustc_infer::infer::InferCtxt;
1616
use rustc_middle::hir::place::ProjectionKind;
1717
use rustc_middle::mir::FakeReadCause;
18-
use rustc_middle::ty::{self, adjustment, Ty, TyCtxt};
18+
use rustc_middle::ty::{self, adjustment, AdtKind, Ty, TyCtxt};
1919
use rustc_target::abi::VariantIdx;
2020
use std::iter;
2121

@@ -845,5 +845,20 @@ fn delegate_consume<'a, 'tcx>(
845845
}
846846

847847
fn is_multivariant_adt(ty: Ty<'tcx>) -> bool {
848-
if let ty::Adt(def, _) = ty.kind() { def.variants.len() > 1 } else { false }
848+
if let ty::Adt(def, _) = ty.kind() {
849+
// Note that if a non-exhaustive SingleVariant is defined in another crate, we need
850+
// to assume that more cases will be added to the variant in the future. This mean
851+
// that we should handle non-exhaustive SingleVariant the same way we would handle
852+
// a MultiVariant.
853+
// If the variant is not local it must be defined in another crate.
854+
let is_non_exhaustive = match def.adt_kind() {
855+
AdtKind::Struct | AdtKind::Union => {
856+
def.non_enum_variant().is_field_list_non_exhaustive()
857+
}
858+
AdtKind::Enum => def.is_variant_list_non_exhaustive(),
859+
};
860+
def.variants.len() > 1 || (!def.did.is_local() && is_non_exhaustive)
861+
} else {
862+
false
863+
}
849864
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#[non_exhaustive]
2+
pub enum E1 {}
3+
4+
#[non_exhaustive]
5+
pub enum E2 { A, B }
6+
7+
#[non_exhaustive]
8+
pub enum E3 { C }
9+
10+
pub enum E4 { D }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// edition:2021
2+
3+
enum SingleVariant {
4+
A
5+
}
6+
7+
struct TestStruct {
8+
x: i32,
9+
y: i32,
10+
z: i32,
11+
}
12+
13+
fn edge_case_if() {
14+
let sv = SingleVariant::A;
15+
let condition = true;
16+
// sv should not be captured as it is a SingleVariant
17+
let _a = || {
18+
match sv {
19+
SingleVariant::A if condition => (),
20+
_ => ()
21+
}
22+
};
23+
let mut mut_sv = sv;
24+
_a();
25+
26+
// ts should be captured
27+
let ts = TestStruct { x: 1, y: 1, z: 1 };
28+
let _b = || { match ts {
29+
TestStruct{ x: 1, .. } => (),
30+
_ => ()
31+
}};
32+
let mut mut_ts = ts;
33+
//~^ ERROR: cannot move out of `ts` because it is borrowed
34+
_b();
35+
}
36+
37+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error[E0505]: cannot move out of `ts` because it is borrowed
2+
--> $DIR/match-edge-cases_2.rs:32:22
3+
|
4+
LL | let _b = || { match ts {
5+
| -- -- borrow occurs due to use in closure
6+
| |
7+
| borrow of `ts` occurs here
8+
...
9+
LL | let mut mut_ts = ts;
10+
| ^^ move out of `ts` occurs here
11+
LL |
12+
LL | _b();
13+
| -- borrow later used here
14+
15+
error: aborting due to previous error
16+
17+
For more information about this error, try `rustc --explain E0505`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// edition:2021
2+
3+
// aux-build:match_non_exhaustive_lib.rs
4+
5+
/* The error message for non-exhaustive matches on non-local enums
6+
* marked as non-exhaustive should mention the fact that the enum
7+
* is marked as non-exhaustive (issue #85227).
8+
*/
9+
10+
// Ignore non_exhaustive in the same crate
11+
#[non_exhaustive]
12+
enum L1 { A, B }
13+
enum L2 { C }
14+
15+
extern crate match_non_exhaustive_lib;
16+
use match_non_exhaustive_lib::{E1, E2, E3, E4};
17+
18+
fn foo() -> (L1, L2) {todo!()}
19+
fn bar() -> (E1, E2, E3, E4) {todo!()}
20+
21+
fn main() {
22+
let (l1, l2) = foo();
23+
// No error for enums defined in this crate
24+
let _a = || { match l1 { L1::A => (), L1::B => () } };
25+
// (except if the match is already non-exhaustive)
26+
let _b = || { match l1 { L1::A => () } };
27+
//~^ ERROR: non-exhaustive patterns: `B` not covered [E0004]
28+
29+
// l2 should not be captured as it is a non-exhaustive SingleVariant
30+
// defined in this crate
31+
let _c = || { match l2 { L2::C => (), _ => () } };
32+
let mut mut_l2 = l2;
33+
_c();
34+
35+
// E1 is not visibly uninhabited from here
36+
let (e1, e2, e3, e4) = bar();
37+
let _d = || { match e1 {} };
38+
//~^ ERROR: non-exhaustive patterns: type `E1` is non-empty [E0004]
39+
let _e = || { match e2 { E2::A => (), E2::B => () } };
40+
//~^ ERROR: non-exhaustive patterns: `_` not covered [E0004]
41+
let _f = || { match e2 { E2::A => (), E2::B => (), _ => () } };
42+
43+
// e3 should be captured as it is a non-exhaustive SingleVariant
44+
// defined in another crate
45+
let _g = || { match e3 { E3::C => (), _ => () } };
46+
let mut mut_e3 = e3;
47+
//~^ ERROR: cannot move out of `e3` because it is borrowed
48+
_g();
49+
50+
// e4 should not be captured as it is a SingleVariant
51+
let _h = || { match e4 { E4::D => (), _ => () } };
52+
let mut mut_e4 = e4;
53+
_h();
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
error[E0004]: non-exhaustive patterns: `B` not covered
2+
--> $DIR/non-exhaustive-match.rs:26:25
3+
|
4+
LL | enum L1 { A, B }
5+
| ----------------
6+
| | |
7+
| | not covered
8+
| `L1` defined here
9+
...
10+
LL | let _b = || { match l1 { L1::A => () } };
11+
| ^^ pattern `B` not covered
12+
|
13+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
14+
= note: the matched value is of type `L1`
15+
16+
error[E0004]: non-exhaustive patterns: type `E1` is non-empty
17+
--> $DIR/non-exhaustive-match.rs:37:25
18+
|
19+
LL | let _d = || { match e1 {} };
20+
| ^^
21+
|
22+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
23+
= note: the matched value is of type `E1`, which is marked as non-exhaustive
24+
25+
error[E0004]: non-exhaustive patterns: `_` not covered
26+
--> $DIR/non-exhaustive-match.rs:39:25
27+
|
28+
LL | let _e = || { match e2 { E2::A => (), E2::B => () } };
29+
| ^^ pattern `_` not covered
30+
|
31+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
32+
= note: the matched value is of type `E2`, which is marked as non-exhaustive
33+
34+
error[E0505]: cannot move out of `e3` because it is borrowed
35+
--> $DIR/non-exhaustive-match.rs:46:22
36+
|
37+
LL | let _g = || { match e3 { E3::C => (), _ => () } };
38+
| -- -- borrow occurs due to use in closure
39+
| |
40+
| borrow of `e3` occurs here
41+
LL | let mut mut_e3 = e3;
42+
| ^^ move out of `e3` occurs here
43+
LL |
44+
LL | _g();
45+
| -- borrow later used here
46+
47+
error: aborting due to 4 previous errors
48+
49+
Some errors have detailed explanations: E0004, E0505.
50+
For more information about an error, try `rustc --explain E0004`.

0 commit comments

Comments
 (0)