Skip to content

Commit 0cda110

Browse files
committed
lower bindings in the order they're written
1 parent fee2dfd commit 0cda110

File tree

6 files changed

+126
-65
lines changed

6 files changed

+126
-65
lines changed

compiler/rustc_mir_build/src/builder/matches/match_pair.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,19 @@ impl<'tcx> MatchPairTree<'tcx> {
124124
let test_case = match pattern.kind {
125125
PatKind::Missing | PatKind::Wild | PatKind::Error(_) => None,
126126

127-
PatKind::Or { ref pats } => Some(TestCase::Or {
128-
pats: pats.iter().map(|pat| FlatPat::new(place_builder.clone(), pat, cx)).collect(),
129-
}),
127+
PatKind::Or { ref pats } => {
128+
let pats: Box<[FlatPat<'tcx>]> =
129+
pats.iter().map(|pat| FlatPat::new(place_builder.clone(), pat, cx)).collect();
130+
if !pats[0].extra_data.bindings.is_empty() {
131+
// Hold a place for any bindings established in (possibly-nested) or-patterns.
132+
// By only holding a place when bindings are present, we skip over any
133+
// or-patterns that will be simplified by `merge_trivial_subcandidates`. In
134+
// other words, we can assume this expands into subcandidates.
135+
// FIXME(@dianne): this needs updating/removing if we always merge or-patterns
136+
extra_data.bindings.push(super::SubpatternBindings::FromOrPattern);
137+
}
138+
Some(TestCase::Or { pats })
139+
}
130140

131141
PatKind::Range(ref range) => {
132142
if range.is_full_range(cx.tcx) == Some(true) {
@@ -194,12 +204,12 @@ impl<'tcx> MatchPairTree<'tcx> {
194204

195205
// Then push this binding, after any bindings in the subpattern.
196206
if let Some(source) = place {
197-
extra_data.bindings.push(super::Binding {
207+
extra_data.bindings.push(super::SubpatternBindings::One(super::Binding {
198208
span: pattern.span,
199209
source,
200210
var_id: var,
201211
binding_mode: mode,
202-
});
212+
}));
203213
}
204214

205215
None

compiler/rustc_mir_build/src/builder/matches/mod.rs

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,7 @@ struct PatternExtraData<'tcx> {
990990
span: Span,
991991

992992
/// Bindings that must be established.
993-
bindings: Vec<Binding<'tcx>>,
993+
bindings: Vec<SubpatternBindings<'tcx>>,
994994

995995
/// Types that must be asserted.
996996
ascriptions: Vec<Ascription<'tcx>>,
@@ -1005,6 +1005,15 @@ impl<'tcx> PatternExtraData<'tcx> {
10051005
}
10061006
}
10071007

1008+
#[derive(Debug, Clone)]
1009+
enum SubpatternBindings<'tcx> {
1010+
/// A single binding.
1011+
One(Binding<'tcx>),
1012+
/// Holds the place for an or-pattern's bindings. This ensures their drops are scheduled in the
1013+
/// order the primary bindings appear. See rust-lang/rust#142163 for more information.
1014+
FromOrPattern,
1015+
}
1016+
10081017
/// A pattern in a form suitable for lowering the match tree, with all irrefutable
10091018
/// patterns simplified away.
10101019
///
@@ -1220,7 +1229,7 @@ fn traverse_candidate<'tcx, C, T, I>(
12201229
}
12211230
}
12221231

1223-
#[derive(Clone, Debug)]
1232+
#[derive(Clone, Copy, Debug)]
12241233
struct Binding<'tcx> {
12251234
span: Span,
12261235
source: Place<'tcx>,
@@ -1446,12 +1455,7 @@ impl<'tcx> MatchTreeSubBranch<'tcx> {
14461455
span: candidate.extra_data.span,
14471456
success_block: candidate.pre_binding_block.unwrap(),
14481457
otherwise_block: candidate.otherwise_block.unwrap(),
1449-
bindings: parent_data
1450-
.iter()
1451-
.flat_map(|d| &d.bindings)
1452-
.chain(&candidate.extra_data.bindings)
1453-
.cloned()
1454-
.collect(),
1458+
bindings: sub_branch_bindings(parent_data, &candidate.extra_data.bindings),
14551459
ascriptions: parent_data
14561460
.iter()
14571461
.flat_map(|d| &d.ascriptions)
@@ -1484,6 +1488,66 @@ impl<'tcx> MatchTreeBranch<'tcx> {
14841488
}
14851489
}
14861490

1491+
/// Collects the bindings for a [`MatchTreeSubBranch`], preserving the order they appear in the
1492+
/// pattern, as though the or-alternatives chosen in this sub-branch were inlined.
1493+
fn sub_branch_bindings<'tcx>(
1494+
parents: &[PatternExtraData<'tcx>],
1495+
leaf_bindings: &[SubpatternBindings<'tcx>],
1496+
) -> Vec<Binding<'tcx>> {
1497+
// In the common case, all bindings will be in leaves. Allocate to fit the leaf's bindings.
1498+
let mut bindings = Vec::with_capacity(leaf_bindings.len());
1499+
let remainder = push_sub_branch_bindings(&mut bindings, Some(parents), leaf_bindings);
1500+
// Make sure we've included all bindings. We can end up with a non-`None` remainder if there's
1501+
// an unsimplifed or-pattern at the end that doesn't contain bindings.
1502+
if let Some(remainder) = remainder {
1503+
assert!(remainder.iter().all(|parent| parent.bindings.is_empty()));
1504+
assert!(leaf_bindings.is_empty());
1505+
}
1506+
bindings
1507+
}
1508+
1509+
/// Helper for [`sub_branch_bindings`]. Returns any bindings yet to be inlined.
1510+
fn push_sub_branch_bindings<'c, 'tcx>(
1511+
flattened: &mut Vec<Binding<'tcx>>,
1512+
parents: Option<&'c [PatternExtraData<'tcx>]>,
1513+
leaf_bindings: &[SubpatternBindings<'tcx>],
1514+
) -> Option<&'c [PatternExtraData<'tcx>]> {
1515+
match parents {
1516+
None => bug!("can't inline or-pattern bindings: already inlined all bindings"),
1517+
Some(mut parents) => {
1518+
let (bindings, mut remainder) = loop {
1519+
match parents {
1520+
// Base case: only the leaf's bindings remain to be inlined.
1521+
[] => break (leaf_bindings, None),
1522+
// Otherwise, inline the first non-empty descendant.
1523+
[parent, remainder @ ..] => {
1524+
if parent.bindings.is_empty() {
1525+
// Skip over unsimplified or-patterns without bindings.
1526+
parents = remainder;
1527+
} else {
1528+
break (&parent.bindings[..], Some(remainder));
1529+
}
1530+
}
1531+
}
1532+
};
1533+
for subpat_bindings in bindings {
1534+
match subpat_bindings {
1535+
SubpatternBindings::One(binding) => flattened.push(*binding),
1536+
SubpatternBindings::FromOrPattern => {
1537+
// Inline bindings from an or-pattern. By construction, this always
1538+
// corresponds to a subcandidate and its closest descendants (i.e. those
1539+
// from nested or-patterns, but not adjacent or-patterns). To handle
1540+
// adjacent or-patterns, e.g. `(x | x, y | y)`, we update the `remainder` to
1541+
// point to the first descendant candidate from outside this or-pattern.
1542+
remainder = push_sub_branch_bindings(flattened, remainder, leaf_bindings);
1543+
}
1544+
}
1545+
}
1546+
remainder
1547+
}
1548+
}
1549+
}
1550+
14871551
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14881552
pub(crate) enum HasMatchGuard {
14891553
Yes,

compiler/rustc_mir_build/src/builder/matches/util.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@ impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> {
138138

139139
fn visit_candidate(&mut self, candidate: &Candidate<'tcx>) {
140140
for binding in &candidate.extra_data.bindings {
141-
self.visit_binding(binding);
141+
if let super::SubpatternBindings::One(binding) = binding {
142+
self.visit_binding(binding);
143+
}
142144
}
143145
for match_pair in &candidate.match_pairs {
144146
self.visit_match_pair(match_pair);
@@ -147,7 +149,9 @@ impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> {
147149

148150
fn visit_flat_pat(&mut self, flat_pat: &FlatPat<'tcx>) {
149151
for binding in &flat_pat.extra_data.bindings {
150-
self.visit_binding(binding);
152+
if let super::SubpatternBindings::One(binding) = binding {
153+
self.visit_binding(binding);
154+
}
151155
}
152156
for match_pair in &flat_pat.match_pairs {
153157
self.visit_match_pair(match_pair);

tests/ui/drop/or-pattern-drop-order.rs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//@ run-pass
22
//! Test drop order for different ways of declaring pattern bindings involving or-patterns.
3-
//! Currently, it's inconsistent between language constructs (#142163).
3+
//! In particular, are ordered based on the order in which the first occurrence of each binding
4+
//! appears (i.e. the "primary" bindings). Regression test for #142163.
45
56
use std::cell::RefCell;
67
use std::ops::Drop;
@@ -43,11 +44,10 @@ fn main() {
4344
y = LogDrop(o, 1);
4445
});
4546

46-
// When bindings are declared with `let pat = expr;`, bindings within or-patterns are seen last,
47-
// thus they're dropped first.
47+
// `let pat = expr;` should have the same drop order.
4848
assert_drop_order(1..=3, |o| {
49-
// Drops are right-to-left, treating `y` as rightmost: `y`, `z`, `x`.
50-
let (x, Ok(y) | Err(y), z) = (LogDrop(o, 3), Ok(LogDrop(o, 1)), LogDrop(o, 2));
49+
// Drops are right-to-left: `z`, `y`, `x`.
50+
let (x, Ok(y) | Err(y), z) = (LogDrop(o, 3), Ok(LogDrop(o, 2)), LogDrop(o, 1));
5151
});
5252
assert_drop_order(1..=2, |o| {
5353
// The first or-pattern alternative determines the bindings' drop order: `y`, `x`.
@@ -58,11 +58,10 @@ fn main() {
5858
let ((true, x, y) | (false, y, x)) = (false, LogDrop(o, 1), LogDrop(o, 2));
5959
});
6060

61-
// `match` treats or-patterns as last like `let pat = expr;`, but also determines drop order
62-
// using the order of the bindings in the *last* or-pattern alternative.
61+
// `match` should have the same drop order.
6362
assert_drop_order(1..=3, |o| {
64-
// Drops are right-to-left, treating `y` as rightmost: `y`, `z`, `x`.
65-
match (LogDrop(o, 3), Ok(LogDrop(o, 1)), LogDrop(o, 2)) { (x, Ok(y) | Err(y), z) => {} }
63+
// Drops are right-to-left: `z`, `y` `x`.
64+
match (LogDrop(o, 3), Ok(LogDrop(o, 2)), LogDrop(o, 1)) { (x, Ok(y) | Err(y), z) => {} }
6665
});
6766
assert_drop_order(1..=2, |o| {
6867
// The first or-pattern alternative determines the bindings' drop order: `y`, `x`.
@@ -74,27 +73,26 @@ fn main() {
7473
});
7574

7675
// Function params are visited one-by-one, and the order of bindings within a param's pattern is
77-
// the same as `let pat = expr`;
76+
// the same as `let pat = expr;`
7877
assert_drop_order(1..=3, |o| {
7978
// Among separate params, the drop order is right-to-left: `z`, `y`, `x`.
8079
(|x, (Ok(y) | Err(y)), z| {})(LogDrop(o, 3), Ok(LogDrop(o, 2)), LogDrop(o, 1));
8180
});
8281
assert_drop_order(1..=3, |o| {
83-
// Within a param's pattern, or-patterns are treated as rightmost: `y`, `z`, `x`.
84-
(|(x, Ok(y) | Err(y), z)| {})((LogDrop(o, 3), Ok(LogDrop(o, 1)), LogDrop(o, 2)));
82+
// Within a param's pattern, likewise: `z`, `y`, `x`.
83+
(|(x, Ok(y) | Err(y), z)| {})((LogDrop(o, 3), Ok(LogDrop(o, 2)), LogDrop(o, 1)));
8584
});
8685
assert_drop_order(1..=2, |o| {
8786
// The first or-pattern alternative determines the bindings' drop order: `y`, `x`.
8887
(|((true, x, y) | (false, y, x))| {})((true, LogDrop(o, 2), LogDrop(o, 1)));
8988
});
9089

9190
// `if let` and `let`-`else` see bindings in the same order as `let pat = expr;`.
92-
// Vars in or-patterns are seen last (dropped first), and the first alternative's order is used.
9391
assert_drop_order(1..=3, |o| {
94-
if let (x, Ok(y) | Err(y), z) = (LogDrop(o, 3), Ok(LogDrop(o, 1)), LogDrop(o, 2)) {}
92+
if let (x, Ok(y) | Err(y), z) = (LogDrop(o, 3), Ok(LogDrop(o, 2)), LogDrop(o, 1)) {}
9593
});
9694
assert_drop_order(1..=3, |o| {
97-
let (x, Ok(y) | Err(y), z) = (LogDrop(o, 3), Ok(LogDrop(o, 1)), LogDrop(o, 2)) else {
95+
let (x, Ok(y) | Err(y), z) = (LogDrop(o, 3), Ok(LogDrop(o, 2)), LogDrop(o, 1)) else {
9896
unreachable!();
9997
};
10098
});
@@ -106,4 +104,21 @@ fn main() {
106104
unreachable!();
107105
};
108106
});
107+
108+
// Test nested and adjacent or-patterns, including or-patterns without bindings under a guard.
109+
assert_drop_order(1..=6, |o| {
110+
// The `LogDrop`s that aren't moved into bindings are dropped last.
111+
match [
112+
[LogDrop(o, 6), LogDrop(o, 4)],
113+
[LogDrop(o, 3), LogDrop(o, 2)],
114+
[LogDrop(o, 1), LogDrop(o, 5)],
115+
] {
116+
[
117+
[_ | _, w | w] | [w | w, _ | _],
118+
[x | x, y | y] | [y | y, x | x],
119+
[z | z, _ | _] | [_ | _, z | z],
120+
] if true => {}
121+
_ => unreachable!(),
122+
}
123+
});
109124
}

tests/ui/pattern/bindings-after-at/bind-by-copy-or-pat.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
//@ known-bug: unknown
1+
//@ run-pass
22
#![allow(unused)]
33

44
struct A(u32);
55

66
pub fn main() {
7-
// The or-pattern bindings are lowered after `x`, which triggers the error.
7+
// Bindings are lowered in the order they appear syntactically, so this works.
88
let x @ (A(a) | A(a)) = A(10);
9-
// ERROR: use of moved value
109
assert!(x.0 == 10);
1110
assert!(a == 10);
1211

13-
// This works.
12+
// This also works.
1413
let (x @ A(a) | x @ A(a)) = A(10);
1514
assert!(x.0 == 10);
1615
assert!(a == 10);

tests/ui/pattern/bindings-after-at/bind-by-copy-or-pat.stderr

Lines changed: 0 additions & 31 deletions
This file was deleted.

0 commit comments

Comments
 (0)