mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-27 18:57:42 +03:00
Auto merge of #150681 - meithecatte:always-discriminate, r=JonathanBrouwer,Nadrieril
Make operational semantics of pattern matching independent of crate and module
The question of "when does matching an enum against a pattern of one of its variants read its discriminant" is currently an underspecified part of the language, causing weird behavior around borrowck, drop order, and UB.
Of course, in the common cases, the discriminant must be read to distinguish the variant of the enum, but currently the following exceptions are implemented:
1. If the enum has only one variant, we currently skip the discriminant read.
- This has the advantage that single-variant enums behave the same way as structs in this regard.
- However, it means that if the discriminant exists in the layout, we can't say that this discriminant being invalid is UB. This makes me particularly uneasy in its interactions with niches – consider the following example ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=5904a6155cbdd39af4a2e7b1d32a9b1a)), where miri currently doesn't detect any UB (because the semantics don't specify any):
<details><summary>Example 1</summary>
```rust
#![allow(dead_code)]
use core::mem::{size_of, transmute};
#[repr(u8)]
enum Inner {
X(u8),
}
enum Outer {
A(Inner),
B(u8),
}
fn f(x: &Inner) {
match x {
Inner::X(v) => {
println!("{v}");
}
}
}
fn main() {
assert_eq!(size_of::<Inner>(), 2);
assert_eq!(size_of::<Outer>(), 2);
let x = Outer::B(42);
let y = &x;
f(unsafe { transmute(y) });
}
```
</details>
2. For the purpose of the above, enums with marked with `#[non_exhaustive]` are always considered to have multiple variants when observed from foreign crates, but the actual number of variants is considered in the current crate.
- This means that whether code has UB can depend on which crate it is in: https://github.com/rust-lang/rust/issues/147722
- In another case of `#[non_exhaustive]` affecting the runtime semantics, its presence or absence can change what gets captured by a closure, and by extension, the drop order: https://github.com/rust-lang/rust/issues/147722#issuecomment-3674554872
- Also at the above link, there is an example where removing `#[non_exhaustive]` can cause borrowck to suddenly start failing in another crate.
3. Moreover, we currently make a more specific check: we only read the discriminant if there is more than one *inhabited* variant in the enum.
- This means that the semantics can differ between `foo<!>`, and a copy of `foo` where `T` was manually replaced with `!`: rust-lang/rust#146803
- Moreover, due to the privacy rules for inhabitedness, it means that the semantics of code can depend on the *module* in which it is located.
- Additionally, this inhabitedness rule is even uglier due to the fact that closure capture analysis needs to happen before we can determine whether types are uninhabited, which means that whether the discriminant read happens has a different answer specifically for capture analysis.
- For the two above points, see the following example ([playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=a07d8a3ec0b31953942e96e2130476d9)):
<details><summary>Example 2</summary>
```rust
#![allow(unused)]
mod foo {
enum Never {}
struct PrivatelyUninhabited(Never);
pub enum A {
V(String, String),
Y(PrivatelyUninhabited),
}
fn works(mut x: A) {
let a = match x {
A::V(ref mut a, _) => a,
_ => unreachable!(),
};
let b = match x {
A::V(_, ref mut b) => b,
_ => unreachable!(),
};
a.len(); b.len();
}
fn fails(mut x: A) {
let mut f = || match x {
A::V(ref mut a, _) => (),
_ => unreachable!(),
};
let mut g = || match x {
A::V(_, ref mut b) => (),
_ => unreachable!(),
};
f(); g();
}
}
use foo::A;
fn fails(mut x: A) {
let a = match x {
A::V(ref mut a, _) => a,
_ => unreachable!(),
};
let b = match x {
A::V(_, ref mut b) => b,
_ => unreachable!(),
};
a.len(); b.len();
}
fn fails2(mut x: A) {
let mut f = || match x {
A::V(ref mut a, _) => (),
_ => unreachable!(),
};
let mut g = || match x {
A::V(_, ref mut b) => (),
_ => unreachable!(),
};
f(); g();
}
```
</details>
In light of the above, and following the discussion at rust-lang/rust#138961 and rust-lang/rust#147722, this PR ~~makes it so that, operationally, matching on an enum *always* reads its discriminant.~~ introduces the following changes to this behavior:
- matching on a `#[non_exhaustive]` enum will always introduce a discriminant read, regardless of whether the enum is from an external crate
- uninhabited variants now count just like normal ones, and don't get skipped in the checks
As per the discussion below, the resolution for point (1) above is that it should land as part of a separate PR, so that the subtler decision can be more carefully considered.
Note that this is a breaking change, due to the aforementioned changes in borrow checking behavior, new UB (or at least UB newly detected by miri), as well as drop order around closure captures. However, it seems to me that the combination of this PR with rust-lang/rust#138961 should have smaller real-world impact than rust-lang/rust#138961 by itself.
Fixes rust-lang/rust#142394
Fixes rust-lang/rust#146590
Fixes rust-lang/rust#146803 (though already marked as duplicate)
Fixes parts of rust-lang/rust#147722
Fixes rust-lang/miri#4778
r? @Nadrieril @RalfJung
@rustbot label +A-closures +A-patterns +T-opsem +T-lang
This commit is contained in:
@@ -819,14 +819,12 @@ fn walk_arm(
|
||||
/// 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.
|
||||
@@ -1853,26 +1851,13 @@ fn pat_deref_place(
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
@@ -291,22 +291,18 @@ pub(super) fn for_pattern(
|
||||
}
|
||||
}
|
||||
|
||||
PatKind::Variant { adt_def, variant_index, args, ref subpatterns } => {
|
||||
PatKind::Variant { adt_def, variant_index, args: _, ref subpatterns } => {
|
||||
let downcast_place = place_builder.downcast(adt_def, variant_index); // `(x as Variant)`
|
||||
cx.field_match_pairs(&mut subpairs, extra_data, downcast_place, subpatterns);
|
||||
|
||||
let irrefutable = adt_def.variants().iter_enumerated().all(|(i, v)| {
|
||||
i == variant_index
|
||||
|| !v.inhabited_predicate(cx.tcx, adt_def).instantiate(cx.tcx, args).apply(
|
||||
cx.tcx,
|
||||
cx.infcx.typing_env(cx.param_env),
|
||||
cx.def_id.into(),
|
||||
)
|
||||
}) && !adt_def.variant_list_has_applicable_non_exhaustive();
|
||||
if irrefutable {
|
||||
None
|
||||
} else {
|
||||
// We treat non-exhaustive enums the same independent of the crate they are
|
||||
// defined in, to avoid differences in the operational semantics between crates.
|
||||
let refutable =
|
||||
adt_def.variants().len() > 1 || adt_def.is_variant_list_non_exhaustive();
|
||||
if refutable {
|
||||
Some(TestableCase::Variant { adt_def, variant_index })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#![allow(deref_nullptr)]
|
||||
|
||||
enum Never {}
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
match *std::ptr::null::<Result<Never, Never>>() {
|
||||
//~^ ERROR: read discriminant of an uninhabited enum variant
|
||||
Ok(_) => {
|
||||
lol();
|
||||
}
|
||||
Err(_) => {
|
||||
wut();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lol() {
|
||||
println!("lol");
|
||||
}
|
||||
|
||||
fn wut() {
|
||||
println!("wut");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
error: Undefined Behavior: read discriminant of an uninhabited enum variant
|
||||
--> tests/fail/match/all_variants_uninhabited.rs:LL:CC
|
||||
|
|
||||
LL | match *std::ptr::null::<Result<Never, Never>>() {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
error: Undefined Behavior: constructing invalid value: encountered a dangling reference (use-after-free)
|
||||
--> tests/fail/closures/deref-in-pattern.rs:LL:CC
|
||||
--> tests/fail/match/closures/deref-in-pattern.rs:LL:CC
|
||||
|
|
||||
LL | let _ = || {
|
||||
| _____________^
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
error: Undefined Behavior: constructing invalid value: encountered a dangling reference (use-after-free)
|
||||
--> tests/fail/closures/partial-pattern.rs:LL:CC
|
||||
--> tests/fail/match/closures/partial-pattern.rs:LL:CC
|
||||
|
|
||||
LL | let _ = || {
|
||||
| _____________^
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
error: Undefined Behavior: read discriminant of an uninhabited enum variant
|
||||
--> tests/fail/closures/uninhabited-variant.rs:LL:CC
|
||||
--> tests/fail/match/closures/uninhabited-variant1.rs:LL:CC
|
||||
|
|
||||
LL | match r {
|
||||
| ^ Undefined Behavior occurred here
|
||||
@@ -8,9 +8,9 @@ LL | match r {
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
= note: stack backtrace:
|
||||
0: main::{closure#0}
|
||||
at tests/fail/closures/uninhabited-variant.rs:LL:CC
|
||||
at tests/fail/match/closures/uninhabited-variant1.rs:LL:CC
|
||||
1: main
|
||||
at tests/fail/closures/uninhabited-variant.rs:LL:CC
|
||||
at tests/fail/match/closures/uninhabited-variant1.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Motivated by rust-lang/rust#138961, this shows how invalid discriminants interact with
|
||||
// closure captures.
|
||||
//
|
||||
// Test case with only one inhabited variant, for which rustc used to not emit
|
||||
// a discriminant read in the first place. See: rust-lang/miri#4778
|
||||
#![feature(never_type)]
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(dead_code)]
|
||||
enum E {
|
||||
V0, // discriminant: 0
|
||||
V1(!), // 1
|
||||
}
|
||||
|
||||
fn main() {
|
||||
assert_eq!(std::mem::size_of::<E>(), 4);
|
||||
|
||||
let val = 1u32;
|
||||
let ptr = (&raw const val).cast::<E>();
|
||||
let r = unsafe { &*ptr };
|
||||
let f = || {
|
||||
// After rust-lang/rust#138961, constructing the closure performs a reborrow of r.
|
||||
// Nevertheless, the discriminant is only actually inspected when the closure
|
||||
// is called.
|
||||
match r { //~ ERROR: read discriminant of an uninhabited enum variant
|
||||
E::V0 => {}
|
||||
E::V1(_) => {}
|
||||
}
|
||||
};
|
||||
|
||||
f();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
error: Undefined Behavior: read discriminant of an uninhabited enum variant
|
||||
--> tests/fail/match/closures/uninhabited-variant2.rs:LL:CC
|
||||
|
|
||||
LL | match r {
|
||||
| ^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
= note: stack backtrace:
|
||||
0: main::{closure#0}
|
||||
at tests/fail/match/closures/uninhabited-variant2.rs:LL:CC
|
||||
1: main
|
||||
at tests/fail/match/closures/uninhabited-variant2.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// rust-lang/miri#4778
|
||||
#![feature(never_type)]
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(dead_code)]
|
||||
enum E {
|
||||
V0, // discriminant: 0
|
||||
V1(!), // 1
|
||||
}
|
||||
|
||||
fn main() {
|
||||
assert_eq!(std::mem::size_of::<E>(), 4);
|
||||
|
||||
let val = 1u32;
|
||||
let ptr = (&raw const val).cast::<E>();
|
||||
let r = unsafe { &*ptr };
|
||||
match r { //~ ERROR: read discriminant of an uninhabited enum variant
|
||||
E::V0 => {}
|
||||
E::V1(_) => {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
error: Undefined Behavior: read discriminant of an uninhabited enum variant
|
||||
--> tests/fail/match/only_inhabited_variant.rs:LL:CC
|
||||
|
|
||||
LL | match r {
|
||||
| ^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Ideally, this would be UB regardless of #[non_exhaustive]. For now,
|
||||
// at least the semantics don't depend on the crate you're in.
|
||||
//
|
||||
// See: rust-lang/rust#147722
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[repr(u8)]
|
||||
enum Exhaustive {
|
||||
A(u8) = 42,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
enum NonExhaustive {
|
||||
A(u8) = 42,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
let x: &[u8; 2] = &[21, 37];
|
||||
let y: &Exhaustive = std::mem::transmute(x);
|
||||
match y {
|
||||
Exhaustive::A(_) => {},
|
||||
}
|
||||
|
||||
let y: &NonExhaustive = std::mem::transmute(x);
|
||||
match y { //~ ERROR: enum value has invalid tag
|
||||
NonExhaustive::A(_) => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
error: Undefined Behavior: enum value has invalid tag: 0x15
|
||||
--> tests/fail/match/single_variant.rs:LL:CC
|
||||
|
|
||||
LL | match y {
|
||||
| ^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Ideally, this would be UB regardless of #[non_exhaustive]. For now,
|
||||
// at least the semantics don't depend on the crate you're in.
|
||||
//
|
||||
// See: rust-lang/rust#147722
|
||||
#![allow(dead_code)]
|
||||
#![allow(unreachable_patterns)]
|
||||
|
||||
#[repr(u8)]
|
||||
enum Exhaustive {
|
||||
A(u8) = 0,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
enum NonExhaustive {
|
||||
A(u8) = 0,
|
||||
}
|
||||
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
fn main() {
|
||||
let buffer: [MaybeUninit<u8>; 2] = [MaybeUninit::uninit(), MaybeUninit::new(0u8)];
|
||||
let exh: *const Exhaustive = (&raw const buffer).cast();
|
||||
let nexh: *const NonExhaustive = (&raw const buffer).cast();
|
||||
unsafe {
|
||||
match *exh {
|
||||
Exhaustive::A(ref _val) => {}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match *nexh { //~ ERROR: memory is uninitialized
|
||||
NonExhaustive::A(ref _val) => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
error: Undefined Behavior: reading memory at ALLOC[0x0..0x1], but memory is uninitialized at [0x0..0x1], and this operation requires initialized memory
|
||||
--> tests/fail/match/single_variant_uninit.rs:LL:CC
|
||||
|
|
||||
LL | match *nexh {
|
||||
| ^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
|
||||
Uninitialized memory occurred at ALLOC[0x0..0x1], in this allocation:
|
||||
ALLOC (stack variable, size: 2, align: 1) {
|
||||
__ 00 │ ░.
|
||||
}
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
@@ -11,6 +11,8 @@ pub enum Never {}
|
||||
pub fn make_unmake_result_never(x: i32) -> i32 {
|
||||
// CHECK-LABEL: define i32 @make_unmake_result_never(i32{{( signext)?}} %x)
|
||||
// CHECK: start:
|
||||
// CHECK-NEXT: br label %[[next:bb.*]]
|
||||
// CHECK: [[next]]:
|
||||
// CHECK-NEXT: ret i32 %x
|
||||
|
||||
let y: Result<i32, Never> = Ok(x);
|
||||
@@ -22,6 +24,8 @@ pub fn make_unmake_result_never(x: i32) -> i32 {
|
||||
pub fn extract_control_flow_never(x: ControlFlow<&str, Never>) -> &str {
|
||||
// CHECK-LABEL: define { ptr, i64 } @extract_control_flow_never(ptr align 1 %x.0, i64 %x.1)
|
||||
// CHECK: start:
|
||||
// CHECK-NEXT: br label %[[next:bb.*]]
|
||||
// CHECK: [[next]]:
|
||||
// CHECK-NEXT: %[[P0:.+]] = insertvalue { ptr, i64 } poison, ptr %x.0, 0
|
||||
// CHECK-NEXT: %[[P1:.+]] = insertvalue { ptr, i64 } %[[P0]], i64 %x.1, 1
|
||||
// CHECK-NEXT: ret { ptr, i64 } %[[P1]]
|
||||
|
||||
@@ -13,17 +13,17 @@ fn opt1(_1: &Result<u32, Void>) -> &u32 {
|
||||
|
||||
bb0: {
|
||||
PlaceMention(_1);
|
||||
falseEdge -> [real: bb4, imaginary: bb1];
|
||||
_2 = discriminant((*_1));
|
||||
switchInt(move _2) -> [0: bb2, 1: bb3, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb1: {
|
||||
_2 = discriminant((*_1));
|
||||
switchInt(move _2) -> [1: bb3, otherwise: bb2];
|
||||
FakeRead(ForMatchedPlace(None), _1);
|
||||
unreachable;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
FakeRead(ForMatchedPlace(None), _1);
|
||||
unreachable;
|
||||
falseEdge -> [real: bb4, imaginary: bb3];
|
||||
}
|
||||
|
||||
bb3: {
|
||||
|
||||
@@ -11,10 +11,25 @@ fn opt2(_1: &Result<u32, Void>) -> &u32 {
|
||||
|
||||
bb0: {
|
||||
PlaceMention(_1);
|
||||
_2 = discriminant((*_1));
|
||||
switchInt(move _2) -> [0: bb2, 1: bb3, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb1: {
|
||||
FakeRead(ForMatchedPlace(None), _1);
|
||||
unreachable;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
StorageLive(_3);
|
||||
_3 = &(((*_1) as Ok).0: u32);
|
||||
_0 = &(*_3);
|
||||
StorageDead(_3);
|
||||
return;
|
||||
}
|
||||
|
||||
bb3: {
|
||||
FakeRead(ForMatchedPlace(None), (((*_1) as Err).0: Void));
|
||||
unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,19 +12,24 @@ fn opt3(_1: &Result<u32, Void>) -> &u32 {
|
||||
bb0: {
|
||||
PlaceMention(_1);
|
||||
_2 = discriminant((*_1));
|
||||
switchInt(move _2) -> [1: bb2, otherwise: bb1];
|
||||
switchInt(move _2) -> [0: bb3, 1: bb2, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb1: {
|
||||
StorageLive(_3);
|
||||
_3 = &(((*_1) as Ok).0: u32);
|
||||
_0 = &(*_3);
|
||||
StorageDead(_3);
|
||||
return;
|
||||
FakeRead(ForMatchedPlace(None), _1);
|
||||
unreachable;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
FakeRead(ForMatchedPlace(None), (((*_1) as Err).0: Void));
|
||||
unreachable;
|
||||
}
|
||||
|
||||
bb3: {
|
||||
StorageLive(_3);
|
||||
_3 = &(((*_1) as Ok).0: u32);
|
||||
_0 = &(*_3);
|
||||
StorageDead(_3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
debug residual => _6;
|
||||
scope 2 {
|
||||
scope 8 (inlined #[track_caller] <Result<i32, i32> as FromResidual<Result<Infallible, i32>>>::from_residual) {
|
||||
let _14: i32;
|
||||
let mut _15: i32;
|
||||
let mut _14: isize;
|
||||
let _15: i32;
|
||||
let mut _16: i32;
|
||||
let mut _17: bool;
|
||||
scope 9 {
|
||||
scope 10 (inlined <i32 as From<i32>>::from) {
|
||||
}
|
||||
@@ -74,10 +76,17 @@
|
||||
StorageLive(_8);
|
||||
_8 = copy _6;
|
||||
StorageLive(_14);
|
||||
_14 = move ((_8 as Err).0: i32);
|
||||
StorageLive(_15);
|
||||
_15 = move _14;
|
||||
_0 = Result::<i32, i32>::Err(move _15);
|
||||
StorageLive(_17);
|
||||
_14 = discriminant(_8);
|
||||
_17 = Eq(copy _14, const 1_isize);
|
||||
assume(move _17);
|
||||
_15 = move ((_8 as Err).0: i32);
|
||||
StorageLive(_16);
|
||||
_16 = move _15;
|
||||
_0 = Result::<i32, i32>::Err(move _16);
|
||||
StorageDead(_16);
|
||||
StorageDead(_17);
|
||||
StorageDead(_15);
|
||||
StorageDead(_14);
|
||||
StorageDead(_8);
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
debug residual => _6;
|
||||
scope 2 {
|
||||
scope 8 (inlined #[track_caller] <Result<i32, i32> as FromResidual<Result<Infallible, i32>>>::from_residual) {
|
||||
let _14: i32;
|
||||
let mut _15: i32;
|
||||
let mut _14: isize;
|
||||
let _15: i32;
|
||||
let mut _16: i32;
|
||||
let mut _17: bool;
|
||||
scope 9 {
|
||||
scope 10 (inlined <i32 as From<i32>>::from) {
|
||||
}
|
||||
@@ -74,10 +76,17 @@
|
||||
StorageLive(_8);
|
||||
_8 = copy _6;
|
||||
StorageLive(_14);
|
||||
_14 = move ((_8 as Err).0: i32);
|
||||
StorageLive(_15);
|
||||
_15 = move _14;
|
||||
_0 = Result::<i32, i32>::Err(move _15);
|
||||
StorageLive(_17);
|
||||
_14 = discriminant(_8);
|
||||
_17 = Eq(copy _14, const 1_isize);
|
||||
assume(move _17);
|
||||
_15 = move ((_8 as Err).0: i32);
|
||||
StorageLive(_16);
|
||||
_16 = move _15;
|
||||
_0 = Result::<i32, i32>::Err(move _16);
|
||||
StorageDead(_16);
|
||||
StorageDead(_17);
|
||||
StorageDead(_15);
|
||||
StorageDead(_14);
|
||||
StorageDead(_8);
|
||||
|
||||
+30
-20
@@ -3,56 +3,66 @@
|
||||
fn map_via_question_mark(_1: Option<i32>) -> Option<i32> {
|
||||
debug x => _1;
|
||||
let mut _0: std::option::Option<i32>;
|
||||
let mut _4: std::ops::ControlFlow<std::option::Option<std::convert::Infallible>, i32>;
|
||||
let _5: i32;
|
||||
let mut _6: i32;
|
||||
let mut _4: std::option::Option<std::convert::Infallible>;
|
||||
let mut _7: std::ops::ControlFlow<std::option::Option<std::convert::Infallible>, i32>;
|
||||
let _8: i32;
|
||||
let mut _9: i32;
|
||||
scope 1 {
|
||||
debug residual => const Option::<Infallible>::None;
|
||||
scope 2 {
|
||||
scope 7 (inlined <Option<i32> as FromResidual<Option<Infallible>>>::from_residual) {
|
||||
let mut _3: isize;
|
||||
let mut _5: bool;
|
||||
}
|
||||
}
|
||||
}
|
||||
scope 3 {
|
||||
debug val => _5;
|
||||
debug val => _8;
|
||||
scope 4 {
|
||||
}
|
||||
}
|
||||
scope 5 (inlined <Option<i32> as Try>::branch) {
|
||||
let mut _2: isize;
|
||||
let _3: i32;
|
||||
let _6: i32;
|
||||
scope 6 {
|
||||
}
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_6);
|
||||
StorageLive(_4);
|
||||
StorageLive(_9);
|
||||
StorageLive(_7);
|
||||
StorageLive(_2);
|
||||
StorageLive(_3);
|
||||
StorageLive(_6);
|
||||
_2 = discriminant(_1);
|
||||
switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb4];
|
||||
}
|
||||
|
||||
bb1: {
|
||||
StorageDead(_3);
|
||||
StorageDead(_2);
|
||||
_0 = const Option::<i32>::None;
|
||||
StorageDead(_6);
|
||||
StorageDead(_4);
|
||||
StorageDead(_2);
|
||||
StorageLive(_3);
|
||||
StorageLive(_5);
|
||||
_3 = discriminant(_4);
|
||||
_5 = Eq(copy _3, const 0_isize);
|
||||
assume(move _5);
|
||||
_0 = const Option::<i32>::None;
|
||||
StorageDead(_5);
|
||||
StorageDead(_3);
|
||||
StorageDead(_9);
|
||||
StorageDead(_7);
|
||||
goto -> bb3;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
_3 = copy ((_1 as Some).0: i32);
|
||||
_4 = ControlFlow::<Option<Infallible>, i32>::Continue(copy _3);
|
||||
StorageDead(_3);
|
||||
StorageDead(_2);
|
||||
_5 = copy ((_4 as Continue).0: i32);
|
||||
_6 = Add(copy _5, const 1_i32);
|
||||
_0 = Option::<i32>::Some(move _6);
|
||||
_6 = copy ((_1 as Some).0: i32);
|
||||
_7 = ControlFlow::<Option<Infallible>, i32>::Continue(copy _6);
|
||||
StorageDead(_6);
|
||||
StorageDead(_4);
|
||||
StorageDead(_2);
|
||||
_8 = copy ((_7 as Continue).0: i32);
|
||||
_9 = Add(copy _8, const 1_i32);
|
||||
_0 = Option::<i32>::Some(move _9);
|
||||
StorageDead(_9);
|
||||
StorageDead(_7);
|
||||
goto -> bb3;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
debug residual => _4;
|
||||
scope 2 {
|
||||
scope 8 (inlined #[track_caller] <Result<i32, i32> as FromResidual<Result<Infallible, i32>>>::from_residual) {
|
||||
let _10: i32;
|
||||
let mut _10: isize;
|
||||
let _11: i32;
|
||||
let mut _12: bool;
|
||||
scope 9 {
|
||||
scope 10 (inlined <i32 as From<i32>>::from) {
|
||||
}
|
||||
@@ -58,8 +60,15 @@
|
||||
|
||||
bb3: {
|
||||
_4 = copy ((_2 as Break).0: std::result::Result<std::convert::Infallible, i32>);
|
||||
_10 = copy ((_4 as Err).0: i32);
|
||||
_0 = Result::<i32, i32>::Err(copy _10);
|
||||
StorageLive(_10);
|
||||
StorageLive(_12);
|
||||
_10 = discriminant(_4);
|
||||
_12 = Eq(copy _10, const 1_isize);
|
||||
assume(move _12);
|
||||
_11 = copy ((_4 as Err).0: i32);
|
||||
_0 = Result::<i32, i32>::Err(copy _11);
|
||||
StorageDead(_12);
|
||||
StorageDead(_10);
|
||||
StorageDead(_2);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -19,19 +19,23 @@
|
||||
|
||||
bb1: {
|
||||
_2 = discriminant(_1);
|
||||
- switchInt(move _2) -> [1: bb3, otherwise: bb2];
|
||||
+ _5 = Ne(copy _2, const 1_isize);
|
||||
- switchInt(move _2) -> [0: bb3, 1: bb4, otherwise: bb2];
|
||||
+ _5 = Eq(copy _2, const 0_isize);
|
||||
+ assume(move _5);
|
||||
+ goto -> bb2;
|
||||
+ goto -> bb3;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
unreachable;
|
||||
}
|
||||
|
||||
bb3: {
|
||||
_0 = const ();
|
||||
StorageDead(_1);
|
||||
return;
|
||||
}
|
||||
|
||||
bb3: {
|
||||
bb4: {
|
||||
- StorageLive(_3);
|
||||
- _3 = move ((_1 as Some).0: Empty);
|
||||
- StorageLive(_4);
|
||||
|
||||
@@ -19,19 +19,23 @@
|
||||
|
||||
bb1: {
|
||||
_2 = discriminant(_1);
|
||||
- switchInt(move _2) -> [1: bb3, otherwise: bb2];
|
||||
+ _5 = Ne(copy _2, const 1_isize);
|
||||
- switchInt(move _2) -> [0: bb3, 1: bb4, otherwise: bb2];
|
||||
+ _5 = Eq(copy _2, const 0_isize);
|
||||
+ assume(move _5);
|
||||
+ goto -> bb2;
|
||||
+ goto -> bb3;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
unreachable;
|
||||
}
|
||||
|
||||
bb3: {
|
||||
_0 = const ();
|
||||
StorageDead(_1);
|
||||
return;
|
||||
}
|
||||
|
||||
bb3: {
|
||||
bb4: {
|
||||
- StorageLive(_3);
|
||||
- _3 = move ((_1 as Some).0: Empty);
|
||||
- StorageLive(_4);
|
||||
|
||||
@@ -45,7 +45,7 @@ fn as_match() {
|
||||
// CHECK: bb0: {
|
||||
// CHECK: {{_.*}} = empty()
|
||||
// CHECK: bb1: {
|
||||
// CHECK: [[eq:_.*]] = Ne({{.*}}, const 1_isize);
|
||||
// CHECK: [[eq:_.*]] = Eq({{.*}}, const 0_isize);
|
||||
// CHECK-NEXT: assume(move [[eq]]);
|
||||
// CHECK-NEXT: goto -> [[return:bb.*]];
|
||||
// CHECK: [[return]]: {
|
||||
|
||||
+14
-14
@@ -14,40 +14,40 @@
|
||||
StorageLive(_2);
|
||||
_2 = Test1::C;
|
||||
_3 = discriminant(_2);
|
||||
- switchInt(move _3) -> [0: bb3, 1: bb2, otherwise: bb1];
|
||||
+ switchInt(move _3) -> [0: bb5, 1: bb5, 2: bb1, otherwise: bb5];
|
||||
- switchInt(move _3) -> [0: bb4, 1: bb3, 2: bb2, otherwise: bb1];
|
||||
+ switchInt(move _3) -> [0: bb1, 1: bb1, 2: bb2, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb1: {
|
||||
unreachable;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
StorageLive(_5);
|
||||
_5 = const "C";
|
||||
_1 = &(*_5);
|
||||
StorageDead(_5);
|
||||
goto -> bb4;
|
||||
goto -> bb5;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
bb3: {
|
||||
StorageLive(_4);
|
||||
_4 = const "B(Empty)";
|
||||
_1 = &(*_4);
|
||||
StorageDead(_4);
|
||||
goto -> bb4;
|
||||
}
|
||||
|
||||
bb3: {
|
||||
_1 = const "A(Empty)";
|
||||
goto -> bb4;
|
||||
goto -> bb5;
|
||||
}
|
||||
|
||||
bb4: {
|
||||
_1 = const "A(Empty)";
|
||||
goto -> bb5;
|
||||
}
|
||||
|
||||
bb5: {
|
||||
StorageDead(_2);
|
||||
StorageDead(_1);
|
||||
_0 = const ();
|
||||
return;
|
||||
+ }
|
||||
+
|
||||
+ bb5: {
|
||||
+ unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-14
@@ -14,40 +14,40 @@
|
||||
StorageLive(_2);
|
||||
_2 = Test1::C;
|
||||
_3 = discriminant(_2);
|
||||
- switchInt(move _3) -> [0: bb3, 1: bb2, otherwise: bb1];
|
||||
+ switchInt(move _3) -> [0: bb5, 1: bb5, 2: bb1, otherwise: bb5];
|
||||
- switchInt(move _3) -> [0: bb4, 1: bb3, 2: bb2, otherwise: bb1];
|
||||
+ switchInt(move _3) -> [0: bb1, 1: bb1, 2: bb2, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb1: {
|
||||
unreachable;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
StorageLive(_5);
|
||||
_5 = const "C";
|
||||
_1 = &(*_5);
|
||||
StorageDead(_5);
|
||||
goto -> bb4;
|
||||
goto -> bb5;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
bb3: {
|
||||
StorageLive(_4);
|
||||
_4 = const "B(Empty)";
|
||||
_1 = &(*_4);
|
||||
StorageDead(_4);
|
||||
goto -> bb4;
|
||||
}
|
||||
|
||||
bb3: {
|
||||
_1 = const "A(Empty)";
|
||||
goto -> bb4;
|
||||
goto -> bb5;
|
||||
}
|
||||
|
||||
bb4: {
|
||||
_1 = const "A(Empty)";
|
||||
goto -> bb5;
|
||||
}
|
||||
|
||||
bb5: {
|
||||
StorageDead(_2);
|
||||
StorageDead(_1);
|
||||
_0 = const ();
|
||||
return;
|
||||
+ }
|
||||
+
|
||||
+ bb5: {
|
||||
+ unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
pub struct LoudDrop(pub &'static str);
|
||||
impl Drop for LoudDrop {
|
||||
fn drop(&mut self) {
|
||||
println!("dropping {}", self.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub enum ExtNonExhaustive {
|
||||
One(i32, LoudDrop),
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#[non_exhaustive]
|
||||
pub enum ExtNonExhaustive {
|
||||
A(u32, String),
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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
|
||||
//@ aux-build:partial_move_drop_order_lib.rs
|
||||
|
||||
extern crate partial_move_drop_order_lib;
|
||||
use partial_move_drop_order_lib::{LoudDrop, ExtNonExhaustive};
|
||||
|
||||
pub enum OneVariant {
|
||||
One(i32, LoudDrop),
|
||||
}
|
||||
|
||||
pub enum TwoVariants {
|
||||
One(i32, LoudDrop),
|
||||
Two,
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub enum NonExhaustive {
|
||||
One(i32, LoudDrop),
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn one_variant() {
|
||||
println!("one variant:");
|
||||
let mut thing = OneVariant::One(0, LoudDrop("a"));
|
||||
let closure = move || match thing {
|
||||
OneVariant::One(x, _) => {}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
println!("before assign");
|
||||
thing = OneVariant::One(1, LoudDrop("b"));
|
||||
println!("after assign");
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn two_variants() {
|
||||
println!("two variants:");
|
||||
let mut thing = TwoVariants::One(0, LoudDrop("a"));
|
||||
let closure = move || match thing {
|
||||
TwoVariants::One(x, _) => {}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
println!("before assign");
|
||||
thing = TwoVariants::One(1, LoudDrop("b"));
|
||||
println!("after assign");
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn non_exhaustive() {
|
||||
println!("non exhaustive:");
|
||||
let mut thing = NonExhaustive::One(0, LoudDrop("a"));
|
||||
let closure = move || match thing {
|
||||
NonExhaustive::One(x, _) => {}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
println!("before assign");
|
||||
thing = NonExhaustive::One(1, LoudDrop("b"));
|
||||
println!("after assign");
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn ext_non_exhaustive() {
|
||||
println!("external non exhaustive:");
|
||||
let mut thing = ExtNonExhaustive::One(0, LoudDrop("a"));
|
||||
let closure = move || match thing {
|
||||
ExtNonExhaustive::One(x, _) => {}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
println!("before assign");
|
||||
thing = ExtNonExhaustive::One(1, LoudDrop("b"));
|
||||
println!("after assign");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
one_variant();
|
||||
println!();
|
||||
two_variants();
|
||||
println!();
|
||||
non_exhaustive();
|
||||
println!();
|
||||
ext_non_exhaustive();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
one variant:
|
||||
before assign
|
||||
dropping a
|
||||
after assign
|
||||
dropping b
|
||||
|
||||
two variants:
|
||||
before assign
|
||||
after assign
|
||||
dropping a
|
||||
dropping b
|
||||
|
||||
non exhaustive:
|
||||
before assign
|
||||
after assign
|
||||
dropping a
|
||||
dropping b
|
||||
|
||||
external non exhaustive:
|
||||
before assign
|
||||
after assign
|
||||
dropping a
|
||||
dropping b
|
||||
@@ -0,0 +1,116 @@
|
||||
// This test measures the effect of matching-induced partial captures on the borrow checker.
|
||||
// In particular, in each of the cases below, the closure either captures the entire enum/struct,
|
||||
// or each field separately.
|
||||
//
|
||||
// If the entire ADT gets captured, it'll happen by move, and the closure will live for 'static.
|
||||
// On the other hand, if each field gets captured separately, the u32 field, being Copy, will only
|
||||
// get captured by an immutable borrow, resulting in a borrow checker error.
|
||||
//
|
||||
// See rust-lang/rust#147722
|
||||
//
|
||||
//@ edition:2021
|
||||
//@ aux-build:partial_move_lib.rs
|
||||
pub struct Struct(u32, String);
|
||||
|
||||
pub enum Enum {
|
||||
A(u32, String),
|
||||
}
|
||||
|
||||
pub enum TwoVariants {
|
||||
A(u32, String),
|
||||
B,
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub enum NonExhaustive {
|
||||
A(u32, String),
|
||||
}
|
||||
|
||||
extern crate partial_move_lib;
|
||||
use partial_move_lib::ExtNonExhaustive;
|
||||
|
||||
// First, let's assert that the additional wildcard arm is not a source of any behavior
|
||||
// differences:
|
||||
pub fn test_enum1(x: Enum) -> impl FnOnce() {
|
||||
|| {
|
||||
//~^ ERROR: closure may outlive the current function, but it borrows `x.0`
|
||||
match x {
|
||||
Enum::A(a, b) => {
|
||||
drop((a, b));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_enum2(x: Enum) -> impl FnOnce() {
|
||||
|| {
|
||||
//~^ ERROR: closure may outlive the current function, but it borrows `x.0`
|
||||
match x {
|
||||
Enum::A(a, b) => {
|
||||
drop((a, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The behavior for single-variant enums matches what happens for a struct
|
||||
pub fn test_struct(x: Struct) -> impl FnOnce() {
|
||||
|| {
|
||||
//~^ ERROR: closure may outlive the current function, but it borrows `x.0`
|
||||
match x {
|
||||
Struct(a, b) => {
|
||||
drop((a, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have two variants, the entire enum gets moved into the closure
|
||||
pub fn test_two_variants(x: TwoVariants) -> impl FnOnce() {
|
||||
|| {
|
||||
match x {
|
||||
TwoVariants::A(a, b) => {
|
||||
drop((a, b));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ...and single-variant, non-exhaustive enums behave as if they had multiple variants
|
||||
pub fn test_non_exhaustive1(x: NonExhaustive) -> impl FnOnce() {
|
||||
|| {
|
||||
match x {
|
||||
NonExhaustive::A(a, b) => {
|
||||
drop((a, b));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (again, wildcard branch or not)
|
||||
pub fn test_non_exhaustive2(x: NonExhaustive) -> impl FnOnce() {
|
||||
|| {
|
||||
match x {
|
||||
NonExhaustive::A(a, b) => {
|
||||
drop((a, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ...regardless of whether the enum is defined in the current, or in another crate
|
||||
pub fn test_ext(x: ExtNonExhaustive) -> impl FnOnce() {
|
||||
|| {
|
||||
match x {
|
||||
ExtNonExhaustive::A(a, b) => {
|
||||
drop((a, b));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,75 @@
|
||||
error[E0373]: closure may outlive the current function, but it borrows `x.0`, which is owned by the current function
|
||||
--> $DIR/partial-move.rs:35: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:35:5
|
||||
|
|
||||
LL | / || {
|
||||
LL | |
|
||||
LL | | match x {
|
||||
LL | | Enum::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:47: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:47:5
|
||||
|
|
||||
LL | / || {
|
||||
LL | |
|
||||
LL | | match x {
|
||||
LL | | Enum::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:59: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:59:5
|
||||
|
|
||||
LL | / || {
|
||||
LL | |
|
||||
LL | | match x {
|
||||
LL | | Struct(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 3 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0373`.
|
||||
@@ -0,0 +1,55 @@
|
||||
// See: rust-lang/rust#146590
|
||||
|
||||
enum Never {}
|
||||
|
||||
// baseline
|
||||
fn both_inhabited(x: &mut Result<String, String>) {
|
||||
match x {
|
||||
&mut Ok(ref mut y) => match x {
|
||||
//~^ ERROR: cannot use `*x` because it was mutably borrowed
|
||||
&mut Err(ref mut z) => {
|
||||
let _y = y;
|
||||
let _z = z;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
// this used to be accepted, even though it shouldn't
|
||||
fn ref_uninhabited(x: &mut Result<Never, String>) {
|
||||
match x {
|
||||
&mut Ok(ref mut y) => match x {
|
||||
//~^ ERROR: cannot use `*x` because it was mutably borrowed
|
||||
&mut Err(ref mut z) => {
|
||||
let _y = y;
|
||||
let _z = z;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
enum Single {
|
||||
V(String, String),
|
||||
}
|
||||
|
||||
// arguably this should be rejected as well, but currently it is still accepted
|
||||
fn single_variant(x: &mut Single) {
|
||||
match x {
|
||||
&mut Single::V(ref mut y, _) => {
|
||||
match x {
|
||||
&mut Single::V(_, ref mut z) => {
|
||||
let _y = y;
|
||||
let _z = z;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,25 @@
|
||||
error[E0503]: cannot use `*x` because it was mutably borrowed
|
||||
--> $DIR/borrowck-uninhabited.rs:8:37
|
||||
|
|
||||
LL | &mut Ok(ref mut y) => match x {
|
||||
| --------- ^ use of borrowed `x.0`
|
||||
| |
|
||||
| `x.0` is borrowed here
|
||||
...
|
||||
LL | let _y = y;
|
||||
| - borrow later used here
|
||||
|
||||
error[E0503]: cannot use `*x` because it was mutably borrowed
|
||||
--> $DIR/borrowck-uninhabited.rs:23:37
|
||||
|
|
||||
LL | &mut Ok(ref mut y) => match x {
|
||||
| --------- ^ use of borrowed `x.0`
|
||||
| |
|
||||
| `x.0` is borrowed here
|
||||
...
|
||||
LL | let _y = y;
|
||||
| - borrow later used here
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0503`.
|
||||
@@ -0,0 +1,46 @@
|
||||
// See rust-lang/rust#146590, as well as Zulip discussion:
|
||||
//
|
||||
// https://rust-lang.zulipchat.com/#narrow/channel/513289-t-patterns/topic/Question.20about.20patterns.20and.20moves/with/558638455
|
||||
//
|
||||
// Whether pattern matching performs a discriminant read shouldn't depend on whether
|
||||
// you explicitly write down an uninhabited branch, or leave it implicit.
|
||||
|
||||
enum Emp { }
|
||||
|
||||
enum Foo<A> {
|
||||
Bar(A),
|
||||
Qux(Emp),
|
||||
}
|
||||
|
||||
fn test1(thefoo: Foo<(Box<u64>, Box<u64>)>) {
|
||||
match thefoo {
|
||||
Foo::Bar((a, _)) => { }
|
||||
}
|
||||
|
||||
match thefoo { //~ ERROR: use of partially moved value: `thefoo`
|
||||
Foo::Bar((_, a)) => { }
|
||||
}
|
||||
}
|
||||
|
||||
fn test2(thefoo: Foo<(Box<u64>, Box<u64>)>) {
|
||||
match thefoo {
|
||||
Foo::Bar((a, _)) => { }
|
||||
Foo::Qux(_) => { }
|
||||
}
|
||||
match thefoo { //~ ERROR: use of partially moved value: `thefoo`
|
||||
Foo::Bar((_, a)) => { }
|
||||
Foo::Qux(_) => { }
|
||||
}
|
||||
}
|
||||
|
||||
fn test3(thefoo: Foo<(Box<u64>, Box<u64>)>) {
|
||||
match thefoo {
|
||||
Foo::Bar((a, _)) => { }
|
||||
Foo::Qux(_) => { }
|
||||
}
|
||||
match thefoo { //~ ERROR: use of partially moved value: `thefoo`
|
||||
Foo::Bar((_, a)) => { }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,48 @@
|
||||
error[E0382]: use of partially moved value: `thefoo`
|
||||
--> $DIR/uninhabited-granular-moves.rs:20:11
|
||||
|
|
||||
LL | Foo::Bar((a, _)) => { }
|
||||
| - value partially moved here
|
||||
...
|
||||
LL | match thefoo {
|
||||
| ^^^^^^ value used here after partial move
|
||||
|
|
||||
= note: partial move occurs because value has type `Box<u64>`, which does not implement the `Copy` trait
|
||||
help: borrow this binding in the pattern to avoid moving the value
|
||||
|
|
||||
LL | Foo::Bar((ref a, _)) => { }
|
||||
| +++
|
||||
|
||||
error[E0382]: use of partially moved value: `thefoo`
|
||||
--> $DIR/uninhabited-granular-moves.rs:30:11
|
||||
|
|
||||
LL | Foo::Bar((a, _)) => { }
|
||||
| - value partially moved here
|
||||
...
|
||||
LL | match thefoo {
|
||||
| ^^^^^^ value used here after partial move
|
||||
|
|
||||
= note: partial move occurs because value has type `Box<u64>`, which does not implement the `Copy` trait
|
||||
help: borrow this binding in the pattern to avoid moving the value
|
||||
|
|
||||
LL | Foo::Bar((ref a, _)) => { }
|
||||
| +++
|
||||
|
||||
error[E0382]: use of partially moved value: `thefoo`
|
||||
--> $DIR/uninhabited-granular-moves.rs:41:11
|
||||
|
|
||||
LL | Foo::Bar((a, _)) => { }
|
||||
| - value partially moved here
|
||||
...
|
||||
LL | match thefoo {
|
||||
| ^^^^^^ value used here after partial move
|
||||
|
|
||||
= note: partial move occurs because value has type `Box<u64>`, which does not implement the `Copy` trait
|
||||
help: borrow this binding in the pattern to avoid moving the value
|
||||
|
|
||||
LL | Foo::Bar((ref a, _)) => { }
|
||||
| +++
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0382`.
|
||||
@@ -14,11 +14,6 @@ enum Local {
|
||||
Variant(u32),
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
enum LocalNonExhaustive {
|
||||
Variant(u32),
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut x = ExhaustiveMonovariant::Variant(1);
|
||||
let y = &mut x;
|
||||
@@ -34,11 +29,4 @@ fn main() {
|
||||
_ => {},
|
||||
}
|
||||
drop(y);
|
||||
let mut x = LocalNonExhaustive::Variant(1);
|
||||
let y = &mut x;
|
||||
match x {
|
||||
LocalNonExhaustive::Variant(_) => {},
|
||||
_ => {},
|
||||
}
|
||||
drop(y);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
|
||||
use monovariants::NonExhaustiveMonovariant;
|
||||
|
||||
#[non_exhaustive]
|
||||
enum LocalNonExhaustive {
|
||||
Variant(u32),
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut x = NonExhaustiveMonovariant::Variant(1);
|
||||
let y = &mut x;
|
||||
@@ -15,4 +20,11 @@ fn main() {
|
||||
_ => {},
|
||||
}
|
||||
drop(y);
|
||||
let mut x = LocalNonExhaustive::Variant(1);
|
||||
let y = &mut x;
|
||||
match x { //~ ERROR cannot use `x` because it was mutably borrowed
|
||||
LocalNonExhaustive::Variant(_) => {},
|
||||
_ => {},
|
||||
}
|
||||
drop(y);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
error[E0503]: cannot use `x` because it was mutably borrowed
|
||||
--> $DIR/borrowck-non-exhaustive.rs:12:11
|
||||
--> $DIR/borrowck-non-exhaustive.rs:17:11
|
||||
|
|
||||
LL | let y = &mut x;
|
||||
| ------ `x` is borrowed here
|
||||
@@ -9,6 +9,17 @@ LL | match x {
|
||||
LL | drop(y);
|
||||
| - borrow later used here
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
error[E0503]: cannot use `x` because it was mutably borrowed
|
||||
--> $DIR/borrowck-non-exhaustive.rs:25:11
|
||||
|
|
||||
LL | let y = &mut x;
|
||||
| ------ `x` is borrowed here
|
||||
LL | match x {
|
||||
| ^ use of borrowed `x`
|
||||
...
|
||||
LL | drop(y);
|
||||
| - borrow later used here
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0503`.
|
||||
|
||||
Reference in New Issue
Block a user