1
Fork 0

Rollup merge of #123324 - Nadrieril:false-edges2, r=matthewjasper

match lowering: make false edges more precise

When lowering match expressions, we add false edges to hide details of the lowering from borrowck. Morally we pretend we're testing the patterns (and guards) one after the other in order. See the tests for examples. Problem is, the way we implement this today is too coarse for deref patterns.

In deref patterns, a pattern like `deref [1, x]` matches on a `Vec` by creating a temporary to store the output of the call to `deref()` and then uses that to continue matching. Here the pattern has a binding, which we set up after the pre-binding block. Problem is, currently the false edges tell borrowck that the pre-binding block can be reached from a previous arm as well, so the `deref()` temporary may not be initialized. This triggers an error when we try to use the binding `x`.

We could call `deref()` a second time, but this opens the door to soundness issues if the deref impl is weird. Instead in this PR I rework false edges a little bit.

What we need from false edges is a (fake) path from each candidate to the next, specifically from candidate C's pre-binding block to next candidate D's pre-binding block. Today, we link the pre-binding blocks directly. In this PR, I link them indirectly by choosing an earlier node on D's success path. Specifically, I choose the earliest block on D's success path that doesn't make a loop (if I chose e.g. the start block of the whole match (which is on the success path of all candidates), that would make a loop). This turns out to be rather straightforward to implement.

r? `@matthewjasper` if you have the bandwidth, otherwise let me know
This commit is contained in:
Matthias Krüger 2024-04-04 14:51:16 +02:00 committed by GitHub
commit 504a78e2f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 345 additions and 95 deletions

View file

@ -214,12 +214,77 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
/// ///
/// ## False edges /// ## False edges
/// ///
/// We don't want to have the exact structure of the decision tree be /// We don't want to have the exact structure of the decision tree be visible through borrow
/// visible through borrow checking. False edges ensure that the CFG as /// checking. Specifically we want borrowck to think that:
/// seen by borrow checking doesn't encode this. False edges are added: /// - at any point, any or none of the patterns and guards seen so far may have been tested;
/// - after the match, any of the patterns may have matched.
/// ///
/// * From each pre-binding block to the next pre-binding block. /// For example, all of these would fail to error if borrowck could see the real CFG (examples
/// * From each otherwise block to the next pre-binding block. /// taken from `tests/ui/nll/match-cfg-fake-edges.rs`):
/// ```ignore (too many errors, this is already in the test suite)
/// let x = String::new();
/// let _ = match true {
/// _ => {},
/// _ => drop(x),
/// };
/// // Borrowck must not know the second arm is never run.
/// drop(x); //~ ERROR use of moved value
///
/// let x;
/// # let y = true;
/// match y {
/// _ if { x = 2; true } => {},
/// // Borrowck must not know the guard is always run.
/// _ => drop(x), //~ ERROR used binding `x` is possibly-uninitialized
/// };
///
/// let x = String::new();
/// # let y = true;
/// match y {
/// false if { drop(x); true } => {},
/// // Borrowck must not know the guard is not run in the `true` case.
/// true => drop(x), //~ ERROR use of moved value: `x`
/// false => {},
/// };
///
/// # let mut y = (true, true);
/// let r = &mut y.1;
/// match y {
/// //~^ ERROR cannot use `y.1` because it was mutably borrowed
/// (false, true) => {}
/// // Borrowck must not know we don't test `y.1` when `y.0` is `true`.
/// (true, _) => drop(r),
/// (false, _) => {}
/// };
/// ```
///
/// We add false edges to act as if we were naively matching each arm in order. What we need is
/// a (fake) path from each candidate to the next, specifically from candidate C's pre-binding
/// block to next candidate D's pre-binding block. For maximum precision (needed for deref
/// patterns), we choose the earliest node on D's success path that doesn't also lead to C (to
/// avoid loops).
///
/// This turns out to be easy to compute: that block is the `start_block` of the first call to
/// `match_candidates` where D is the first candidate in the list.
///
/// For example:
/// ```rust
/// # let (x, y) = (true, true);
/// match (x, y) {
/// (true, true) => 1,
/// (false, true) => 2,
/// (true, false) => 3,
/// _ => 4,
/// }
/// # ;
/// ```
/// In this example, the pre-binding block of arm 1 has a false edge to the block for result
/// `false` of the first test on `x`. The other arms have false edges to the pre-binding blocks
/// of the next arm.
///
/// On top of this, we also add a false edge from the otherwise_block of each guard to the
/// aforementioned start block of the next candidate, to ensure borrock doesn't rely on which
/// guards may have run.
#[instrument(level = "debug", skip(self, arms))] #[instrument(level = "debug", skip(self, arms))]
pub(crate) fn match_expr( pub(crate) fn match_expr(
&mut self, &mut self,
@ -365,7 +430,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
for candidate in candidates { for candidate in candidates {
candidate.visit_leaves(|leaf_candidate| { candidate.visit_leaves(|leaf_candidate| {
if let Some(ref mut prev) = previous_candidate { if let Some(ref mut prev) = previous_candidate {
prev.next_candidate_pre_binding_block = leaf_candidate.pre_binding_block; assert!(leaf_candidate.false_edge_start_block.is_some());
prev.next_candidate_start_block = leaf_candidate.false_edge_start_block;
} }
previous_candidate = Some(leaf_candidate); previous_candidate = Some(leaf_candidate);
}); });
@ -1010,8 +1076,12 @@ struct Candidate<'pat, 'tcx> {
/// The block before the `bindings` have been established. /// The block before the `bindings` have been established.
pre_binding_block: Option<BasicBlock>, pre_binding_block: Option<BasicBlock>,
/// The pre-binding block of the next candidate.
next_candidate_pre_binding_block: Option<BasicBlock>, /// The earliest block that has only candidates >= this one as descendents. Used for false
/// edges, see the doc for [`Builder::match_expr`].
false_edge_start_block: Option<BasicBlock>,
/// The `false_edge_start_block` of the next candidate.
next_candidate_start_block: Option<BasicBlock>,
} }
impl<'tcx, 'pat> Candidate<'pat, 'tcx> { impl<'tcx, 'pat> Candidate<'pat, 'tcx> {
@ -1033,7 +1103,8 @@ impl<'tcx, 'pat> Candidate<'pat, 'tcx> {
or_span: None, or_span: None,
otherwise_block: None, otherwise_block: None,
pre_binding_block: None, pre_binding_block: None,
next_candidate_pre_binding_block: None, false_edge_start_block: None,
next_candidate_start_block: None,
} }
} }
@ -1325,6 +1396,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
otherwise_block: BasicBlock, otherwise_block: BasicBlock,
candidates: &mut [&mut Candidate<'_, 'tcx>], candidates: &mut [&mut Candidate<'_, 'tcx>],
) { ) {
if let [first, ..] = candidates {
if first.false_edge_start_block.is_none() {
first.false_edge_start_block = Some(start_block);
}
}
match candidates { match candidates {
[] => { [] => {
// If there are no candidates that still need testing, we're done. Since all matches are // If there are no candidates that still need testing, we're done. Since all matches are
@ -1545,6 +1622,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
.into_iter() .into_iter()
.map(|flat_pat| Candidate::from_flat_pat(flat_pat, candidate.has_guard)) .map(|flat_pat| Candidate::from_flat_pat(flat_pat, candidate.has_guard))
.collect(); .collect();
candidate.subcandidates[0].false_edge_start_block = candidate.false_edge_start_block;
} }
/// Try to merge all of the subcandidates of the given candidate into one. This avoids /// Try to merge all of the subcandidates of the given candidate into one. This avoids
@ -1564,6 +1642,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
let any_matches = self.cfg.start_new_block(); let any_matches = self.cfg.start_new_block();
let or_span = candidate.or_span.take().unwrap(); let or_span = candidate.or_span.take().unwrap();
let source_info = self.source_info(or_span); let source_info = self.source_info(or_span);
if candidate.false_edge_start_block.is_none() {
candidate.false_edge_start_block =
candidate.subcandidates[0].false_edge_start_block;
}
for subcandidate in mem::take(&mut candidate.subcandidates) { for subcandidate in mem::take(&mut candidate.subcandidates) {
let or_block = subcandidate.pre_binding_block.unwrap(); let or_block = subcandidate.pre_binding_block.unwrap();
self.cfg.goto(or_block, source_info, any_matches); self.cfg.goto(or_block, source_info, any_matches);
@ -1979,12 +2061,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
let mut block = candidate.pre_binding_block.unwrap(); let mut block = candidate.pre_binding_block.unwrap();
if candidate.next_candidate_pre_binding_block.is_some() { if candidate.next_candidate_start_block.is_some() {
let fresh_block = self.cfg.start_new_block(); let fresh_block = self.cfg.start_new_block();
self.false_edges( self.false_edges(
block, block,
fresh_block, fresh_block,
candidate.next_candidate_pre_binding_block, candidate.next_candidate_start_block,
candidate_source_info, candidate_source_info,
); );
block = fresh_block; block = fresh_block;
@ -2132,7 +2214,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
self.false_edges( self.false_edges(
otherwise_post_guard_block, otherwise_post_guard_block,
otherwise_block, otherwise_block,
candidate.next_candidate_pre_binding_block, candidate.next_candidate_start_block,
source_info, source_info,
); );

View file

@ -48,7 +48,7 @@ fn main() -> () {
} }
bb2: { bb2: {
falseEdge -> [real: bb15, imaginary: bb6]; falseEdge -> [real: bb15, imaginary: bb3];
} }
bb3: { bb3: {

View file

@ -40,7 +40,7 @@ fn constant_eq(_1: &str, _2: bool) -> u32 {
} }
bb4: { bb4: {
falseEdge -> [real: bb12, imaginary: bb9]; falseEdge -> [real: bb12, imaginary: bb7];
} }
bb5: { bb5: {
@ -48,7 +48,7 @@ fn constant_eq(_1: &str, _2: bool) -> u32 {
} }
bb6: { bb6: {
falseEdge -> [real: bb16, imaginary: bb3]; falseEdge -> [real: bb16, imaginary: bb1];
} }
bb7: { bb7: {
@ -60,7 +60,7 @@ fn constant_eq(_1: &str, _2: bool) -> u32 {
} }
bb9: { bb9: {
falseEdge -> [real: bb15, imaginary: bb6]; falseEdge -> [real: bb15, imaginary: bb5];
} }
bb10: { bb10: {
@ -89,7 +89,7 @@ fn constant_eq(_1: &str, _2: bool) -> u32 {
bb14: { bb14: {
StorageDead(_10); StorageDead(_10);
falseEdge -> [real: bb5, imaginary: bb9]; falseEdge -> [real: bb5, imaginary: bb7];
} }
bb15: { bb15: {

View file

@ -23,7 +23,7 @@ fn disjoint_ranges(_1: i32, _2: bool) -> u32 {
} }
bb2: { bb2: {
falseEdge -> [real: bb9, imaginary: bb4]; falseEdge -> [real: bb9, imaginary: bb3];
} }
bb3: { bb3: {
@ -32,7 +32,7 @@ fn disjoint_ranges(_1: i32, _2: bool) -> u32 {
} }
bb4: { bb4: {
falseEdge -> [real: bb12, imaginary: bb6]; falseEdge -> [real: bb12, imaginary: bb5];
} }
bb5: { bb5: {
@ -69,7 +69,7 @@ fn disjoint_ranges(_1: i32, _2: bool) -> u32 {
bb11: { bb11: {
StorageDead(_8); StorageDead(_8);
falseEdge -> [real: bb1, imaginary: bb4]; falseEdge -> [real: bb1, imaginary: bb3];
} }
bb12: { bb12: {

View file

@ -60,11 +60,11 @@
} }
- bb5: { - bb5: {
- falseEdge -> [real: bb13, imaginary: bb3]; - falseEdge -> [real: bb13, imaginary: bb2];
- } - }
- -
- bb6: { - bb6: {
- falseEdge -> [real: bb8, imaginary: bb5]; - falseEdge -> [real: bb8, imaginary: bb1];
- } - }
- -
- bb7: { - bb7: {
@ -127,7 +127,7 @@
StorageDead(_9); StorageDead(_9);
StorageDead(_8); StorageDead(_8);
StorageDead(_6); StorageDead(_6);
- falseEdge -> [real: bb1, imaginary: bb5]; - falseEdge -> [real: bb1, imaginary: bb1];
+ goto -> bb1; + goto -> bb1;
} }
@ -184,7 +184,7 @@
StorageDead(_12); StorageDead(_12);
StorageDead(_8); StorageDead(_8);
StorageDead(_6); StorageDead(_6);
- falseEdge -> [real: bb2, imaginary: bb3]; - falseEdge -> [real: bb2, imaginary: bb2];
+ goto -> bb2; + goto -> bb2;
} }

View file

@ -60,11 +60,11 @@
} }
- bb5: { - bb5: {
- falseEdge -> [real: bb13, imaginary: bb3]; - falseEdge -> [real: bb13, imaginary: bb2];
- } - }
- -
- bb6: { - bb6: {
- falseEdge -> [real: bb8, imaginary: bb5]; - falseEdge -> [real: bb8, imaginary: bb1];
- } - }
- -
- bb7: { - bb7: {
@ -127,7 +127,7 @@
StorageDead(_9); StorageDead(_9);
StorageDead(_8); StorageDead(_8);
StorageDead(_6); StorageDead(_6);
- falseEdge -> [real: bb1, imaginary: bb5]; - falseEdge -> [real: bb1, imaginary: bb1];
+ goto -> bb1; + goto -> bb1;
} }
@ -184,7 +184,7 @@
StorageDead(_12); StorageDead(_12);
StorageDead(_8); StorageDead(_8);
StorageDead(_6); StorageDead(_6);
- falseEdge -> [real: bb2, imaginary: bb3]; - falseEdge -> [real: bb2, imaginary: bb2];
+ goto -> bb2; + goto -> bb2;
} }

View file

@ -3,15 +3,57 @@
#![feature(if_let_guard)] #![feature(if_let_guard)]
#[rustfmt::skip]
fn all_patterns_are_tested() {
// Even though `x` is never actually moved out of, we don't want borrowck results to be based on
// whether MIR lowering reveals which patterns are unreachable.
let x = String::new();
match true {
_ => {},
_ => drop(x),
}
// Borrowck must not know the second arm is never run.
drop(x); //~ ERROR use of moved value
let x = String::new();
if let _ = true { //~ WARN irrefutable
} else {
drop(x)
}
// Borrowck must not know the else branch is never run.
drop(x); //~ ERROR use of moved value
let x = (String::new(), String::new());
match x {
(y, _) | (_, y) => (),
}
&x.0; //~ ERROR borrow of moved value
// Borrowck must not know the second pattern never matches.
&x.1; //~ ERROR borrow of moved value
let x = (String::new(), String::new());
let ((y, _) | (_, y)) = x;
&x.0; //~ ERROR borrow of moved value
// Borrowck must not know the second pattern never matches.
&x.1; //~ ERROR borrow of moved value
}
#[rustfmt::skip]
fn guard_always_precedes_arm(y: i32) { fn guard_always_precedes_arm(y: i32) {
let mut x;
// x should always be initialized, as the only way to reach the arm is // x should always be initialized, as the only way to reach the arm is
// through the guard. // through the guard.
let mut x;
match y { match y {
0 | 2 if { x = 2; true } => x, 0 | 2 if { x = 2; true } => x,
_ => 2, _ => 2,
}; };
let mut x;
match y {
_ => 2,
0 | 2 if { x = 2; true } => x,
};
let mut x; let mut x;
match y { match y {
0 | 2 if let Some(()) = { x = 2; Some(()) } => x, 0 | 2 if let Some(()) = { x = 2; Some(()) } => x,
@ -19,51 +61,58 @@ fn guard_always_precedes_arm(y: i32) {
}; };
} }
#[rustfmt::skip]
fn guard_may_be_skipped(y: i32) { fn guard_may_be_skipped(y: i32) {
// Even though x *is* always initialized, we don't want to have borrowck results be based on
// whether MIR lowering reveals which patterns are exhaustive.
let x;
match y {
_ if { x = 2; true } => {},
// Borrowck must not know the guard is always run.
_ => drop(x), //~ ERROR used binding `x` is possibly-uninitialized
};
let x; let x;
// Even though x *is* always initialized, we don't want to have borrowck
// results be based on whether patterns are exhaustive.
match y { match y {
_ if { x = 2; true } => 1, _ if { x = 2; true } => 1,
_ if { // Borrowck must not know the guard is always run.
x; //~ ERROR E0381 _ if { x; false } => 2, //~ ERROR used binding `x` isn't initialized
false
} => 2,
_ => 3, _ => 3,
}; };
let x; let x;
match y { match y {
_ if let Some(()) = { x = 2; Some(()) } => 1, _ if let Some(()) = { x = 2; Some(()) } => 1,
_ if let Some(()) = { _ if let Some(()) = { x; None } => 2, //~ ERROR used binding `x` isn't initialized
x; //~ ERROR E0381
None
} => 2,
_ => 3, _ => 3,
}; };
} }
#[rustfmt::skip]
fn guard_may_be_taken(y: bool) { fn guard_may_be_taken(y: bool) {
let x = String::new();
// Even though x *is* never moved before the use, we don't want to have // Even though x *is* never moved before the use, we don't want to have
// borrowck results be based on whether patterns are disjoint. // borrowck results be based on whether patterns are disjoint.
let x = String::new();
match y { match y {
false if { drop(x); true } => 1, false if { drop(x); true } => {},
true => { // Borrowck must not know the guard is not run in the `true` case.
x; //~ ERROR use of moved value: `x` true => drop(x), //~ ERROR use of moved value: `x`
2 false => {},
} };
false => 3,
// Fine in the other order.
let x = String::new();
match y {
true => drop(x),
false if { drop(x); true } => {},
false => {},
}; };
let x = String::new(); let x = String::new();
match y { match y {
false if let Some(()) = { drop(x); Some(()) } => 1, false if let Some(()) = { drop(x); Some(()) } => {},
true => { true => drop(x), //~ ERROR use of moved value: `x`
x; //~ ERROR use of moved value: `x` false => {},
2
}
false => 3,
}; };
} }

View file

@ -1,14 +1,128 @@
error[E0381]: used binding `x` isn't initialized warning: irrefutable `if let` pattern
--> $DIR/match-cfg-fake-edges.rs:29:13 --> $DIR/match-cfg-fake-edges.rs:19:8
|
LL | if let _ = true {
| ^^^^^^^^^^^^
|
= note: this pattern will always match, so the `if let` is useless
= help: consider replacing the `if let` with a `let`
= note: `#[warn(irrefutable_let_patterns)]` on by default
error[E0382]: use of moved value: `x`
--> $DIR/match-cfg-fake-edges.rs:16:10
|
LL | let x = String::new();
| - move occurs because `x` has type `String`, which does not implement the `Copy` trait
...
LL | _ => drop(x),
| - value moved here
...
LL | drop(x);
| ^ value used here after move
|
help: consider cloning the value if the performance cost is acceptable
|
LL | _ => drop(x.clone()),
| ++++++++
error[E0382]: use of moved value: `x`
--> $DIR/match-cfg-fake-edges.rs:24:10
|
LL | let x = String::new();
| - move occurs because `x` has type `String`, which does not implement the `Copy` trait
...
LL | drop(x)
| - value moved here
...
LL | drop(x);
| ^ value used here after move
|
help: consider cloning the value if the performance cost is acceptable
|
LL | drop(x.clone())
| ++++++++
error[E0382]: borrow of moved value: `x.0`
--> $DIR/match-cfg-fake-edges.rs:30:5
|
LL | (y, _) | (_, y) => (),
| - value moved here
LL | }
LL | &x.0;
| ^^^^ value borrowed here after move
|
= note: move occurs because `x.0` has type `String`, which does not implement the `Copy` trait
help: borrow this binding in the pattern to avoid moving the value
|
LL | (ref y, _) | (_, y) => (),
| +++
error[E0382]: borrow of moved value: `x.1`
--> $DIR/match-cfg-fake-edges.rs:32:5
|
LL | (y, _) | (_, y) => (),
| - value moved here
...
LL | &x.1;
| ^^^^ value borrowed here after move
|
= note: move occurs because `x.1` has type `String`, which does not implement the `Copy` trait
help: borrow this binding in the pattern to avoid moving the value
|
LL | (y, _) | (_, ref y) => (),
| +++
error[E0382]: borrow of moved value: `x.0`
--> $DIR/match-cfg-fake-edges.rs:36:5
|
LL | let ((y, _) | (_, y)) = x;
| - value moved here
LL | &x.0;
| ^^^^ value borrowed here after move
|
= note: move occurs because `x.0` has type `String`, which does not implement the `Copy` trait
help: borrow this binding in the pattern to avoid moving the value
|
LL | let ((ref y, _) | (_, y)) = x;
| +++
error[E0382]: borrow of moved value: `x.1`
--> $DIR/match-cfg-fake-edges.rs:38:5
|
LL | let ((y, _) | (_, y)) = x;
| - value moved here
...
LL | &x.1;
| ^^^^ value borrowed here after move
|
= note: move occurs because `x.1` has type `String`, which does not implement the `Copy` trait
help: borrow this binding in the pattern to avoid moving the value
|
LL | let ((y, _) | (_, ref y)) = x;
| +++
error[E0381]: used binding `x` is possibly-uninitialized
--> $DIR/match-cfg-fake-edges.rs:72:19
| |
LL | let x; LL | let x;
| - binding declared here but left uninitialized | - binding declared here but left uninitialized
... ...
LL | _ => drop(x),
| - ^ `x` used here but it is possibly-uninitialized
| |
| if this pattern is matched, `x` is not initialized
error[E0381]: used binding `x` isn't initialized
--> $DIR/match-cfg-fake-edges.rs:79:16
|
LL | let x;
| - binding declared here but left uninitialized
LL | match y {
LL | _ if { x = 2; true } => 1, LL | _ if { x = 2; true } => 1,
| ----- binding initialized here in some conditions | ----- binding initialized here in some conditions
LL | _ if { LL | // Borrowck must not know the guard is always run.
LL | x; LL | _ if { x; false } => 2,
| ^ `x` used here but it isn't initialized | ^ `x` used here but it isn't initialized
| |
help: consider assigning a value help: consider assigning a value
| |
@ -16,16 +130,15 @@ LL | let x = 0;
| +++ | +++
error[E0381]: used binding `x` isn't initialized error[E0381]: used binding `x` isn't initialized
--> $DIR/match-cfg-fake-edges.rs:39:13 --> $DIR/match-cfg-fake-edges.rs:86:31
| |
LL | let x; LL | let x;
| - binding declared here but left uninitialized | - binding declared here but left uninitialized
LL | match y { LL | match y {
LL | _ if let Some(()) = { x = 2; Some(()) } => 1, LL | _ if let Some(()) = { x = 2; Some(()) } => 1,
| ----- binding initialized here in some conditions | ----- binding initialized here in some conditions
LL | _ if let Some(()) = { LL | _ if let Some(()) = { x; None } => 2,
LL | x; | ^ `x` used here but it isn't initialized
| ^ `x` used here but it isn't initialized
| |
help: consider assigning a value help: consider assigning a value
| |
@ -33,40 +146,39 @@ LL | let x = 0;
| +++ | +++
error[E0382]: use of moved value: `x` error[E0382]: use of moved value: `x`
--> $DIR/match-cfg-fake-edges.rs:53:13 --> $DIR/match-cfg-fake-edges.rs:99:22
|
LL | let x = String::new();
| - move occurs because `x` has type `String`, which does not implement the `Copy` trait
...
LL | false if { drop(x); true } => 1,
| - value moved here
LL | true => {
LL | x;
| ^ value used here after move
|
help: consider cloning the value if the performance cost is acceptable
|
LL | false if { drop(x.clone()); true } => 1,
| ++++++++
error[E0382]: use of moved value: `x`
--> $DIR/match-cfg-fake-edges.rs:63:13
| |
LL | let x = String::new(); LL | let x = String::new();
| - move occurs because `x` has type `String`, which does not implement the `Copy` trait | - move occurs because `x` has type `String`, which does not implement the `Copy` trait
LL | match y { LL | match y {
LL | false if let Some(()) = { drop(x); Some(()) } => 1, LL | false if { drop(x); true } => {},
| - value moved here | - value moved here
LL | true => { LL | // Borrowck must not know the guard is not run in the `true` case.
LL | x; LL | true => drop(x),
| ^ value used here after move | ^ value used here after move
| |
help: consider cloning the value if the performance cost is acceptable help: consider cloning the value if the performance cost is acceptable
| |
LL | false if let Some(()) = { drop(x.clone()); Some(()) } => 1, LL | false if { drop(x.clone()); true } => {},
| ++++++++
error[E0382]: use of moved value: `x`
--> $DIR/match-cfg-fake-edges.rs:114:22
|
LL | let x = String::new();
| - move occurs because `x` has type `String`, which does not implement the `Copy` trait
LL | match y {
LL | false if let Some(()) = { drop(x); Some(()) } => {},
| - value moved here
LL | true => drop(x),
| ^ value used here after move
|
help: consider cloning the value if the performance cost is acceptable
|
LL | false if let Some(()) = { drop(x.clone()); Some(()) } => {},
| ++++++++ | ++++++++
error: aborting due to 4 previous errors error: aborting due to 11 previous errors; 1 warning emitted
Some errors have detailed explanations: E0381, E0382. Some errors have detailed explanations: E0381, E0382.
For more information about an error, try `rustc --explain E0381`. For more information about an error, try `rustc --explain E0381`.

View file

@ -5,13 +5,20 @@ fn all_previous_tests_may_be_done(y: &mut (bool, bool)) {
let r = &mut y.1; let r = &mut y.1;
// We don't actually test y.1 to select the second arm, but we don't want // We don't actually test y.1 to select the second arm, but we don't want
// borrowck results to be based on the order we match patterns. // borrowck results to be based on the order we match patterns.
match y { //~ ERROR cannot use `y.1` because it was mutably borrowed match y {
(false, true) => 1, //~^ ERROR cannot use `y.1` because it was mutably borrowed
(true, _) => { (false, true) => {}
r; // Borrowck must not know we don't test `y.1` when `y.0` is `true`.
2 (true, _) => drop(r),
} (false, _) => {}
(false, _) => 3, };
// Fine in the other order.
let r = &mut y.1;
match y {
(true, _) => drop(r),
(false, true) => {}
(false, _) => {}
}; };
} }

View file

@ -7,8 +7,8 @@ LL | let r = &mut y.1;
LL | match y { LL | match y {
| ^^^^^^^ use of borrowed `y.1` | ^^^^^^^ use of borrowed `y.1`
... ...
LL | r; LL | (true, _) => drop(r),
| - borrow later used here | - borrow later used here
error: aborting due to 1 previous error error: aborting due to 1 previous error