Skip to content

Commit fafe42c

Browse files
Auto merge of #143764 - dianne:primary-binding-drop-order, r=<try>
lower pattern bindings in the order they're written and base drop order on primary bindings' order To fix #142163, this PR does two things: - Makes match arms base their drop order on the first sub-branch instead of the last sub-branch. Together with the second change, this makes bindings' drop order correspond to the relative order of when each binding first appears (i.e. the order of the "primary" bindings). - Lowers pattern bindings in the order they're written (still treating the right-hand side of a ``@`` as coming before the binding on the left). In each sub-branch of a match arm, this is the order that would be obtained if the or-alternatives chosen in that sub-branch were inlined into the arm's pattern. This both affects drop order (making bindings in or-patterns not be dropped first) and fixes the issue in [this test](https://github.com/rust-lang/rust/blob/2a023bf80a6fbd6a06d5460a34eb247b986286ed/tests/ui/pattern/bindings-after-at/bind-by-copy-or-pat.rs) from #121716. My approach to the second point is admittedly a bit trickier than may be necessary. To avoid passing around a counter when building `FlatPat`s, I've instead added just enough information to recover the original structure of the pattern's bindings from a `MatchTreeSubBranch`'s path through the `Candidate` tree. Some alternatives: - We could use a counter, then sort bindings by their ordinals when making `MatchTreeSubBranch`es. - I'd like to experiment with always merging sub-candidates and removing `test_remaining_match_pairs_after_or`; that would require lowering bindings and guards in a different way. That makes it a bigger change too, though, so I figure it might be simplest to start here. - For a very big change, we could track which or-alternatives succeed at runtime to base drop order on the binding order in the particular alternatives matched. This is a breaking change. It will need a crater run, language team sign-off, and likely updates to the Reference. This will conflict with #143376 and probably also #143028, so they shouldn't be merged at the same time. r? `@matthewjasper` or `@Nadrieril`
2 parents 2a023bf + 0cda110 commit fafe42c

File tree

9 files changed

+179
-123
lines changed

9 files changed

+179
-123
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: 95 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
//! This also includes code for pattern bindings in `let` statements and
66
//! function parameters.
77
8-
use std::assert_matches::assert_matches;
98
use std::borrow::Borrow;
109
use std::mem;
1110
use std::sync::Arc;
1211

12+
use itertools::{Itertools, Position};
1313
use rustc_abi::VariantIdx;
1414
use rustc_data_structures::fx::FxIndexMap;
1515
use rustc_data_structures::stack::ensure_sufficient_stack;
@@ -561,16 +561,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
561561
// return: it isn't bound by move until right before enter the arm.
562562
// To handle this we instead unschedule it's drop after each time
563563
// we lower the guard.
564+
// As a result, we end up with the drop order of the last sub-branch we lower. To use
565+
// the drop order for the first sub-branch, we lower sub-branches in reverse (#142163).
564566
let target_block = self.cfg.start_new_block();
565-
let mut schedule_drops = ScheduleDrops::Yes;
566-
let arm = arm_match_scope.unzip().0;
567-
// We keep a stack of all of the bindings and type ascriptions
568-
// from the parent candidates that we visit, that also need to
569-
// be bound for each candidate.
570-
for sub_branch in branch.sub_branches {
571-
if let Some(arm) = arm {
572-
self.clear_top_scope(arm.scope);
573-
}
567+
for (pos, sub_branch) in branch.sub_branches.into_iter().rev().with_position() {
568+
debug_assert!(pos != Position::Only);
569+
let schedule_drops =
570+
if pos == Position::Last { ScheduleDrops::Yes } else { ScheduleDrops::No };
574571
let binding_end = self.bind_and_guard_matched_candidate(
575572
sub_branch,
576573
fake_borrow_temps,
@@ -579,9 +576,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
579576
schedule_drops,
580577
emit_storage_live,
581578
);
582-
if arm.is_none() {
583-
schedule_drops = ScheduleDrops::No;
584-
}
585579
self.cfg.goto(binding_end, outer_source_info, target_block);
586580
}
587581

@@ -996,7 +990,7 @@ struct PatternExtraData<'tcx> {
996990
span: Span,
997991

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

1001995
/// Types that must be asserted.
1002996
ascriptions: Vec<Ascription<'tcx>>,
@@ -1011,6 +1005,15 @@ impl<'tcx> PatternExtraData<'tcx> {
10111005
}
10121006
}
10131007

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+
10141017
/// A pattern in a form suitable for lowering the match tree, with all irrefutable
10151018
/// patterns simplified away.
10161019
///
@@ -1226,7 +1229,7 @@ fn traverse_candidate<'tcx, C, T, I>(
12261229
}
12271230
}
12281231

1229-
#[derive(Clone, Debug)]
1232+
#[derive(Clone, Copy, Debug)]
12301233
struct Binding<'tcx> {
12311234
span: Span,
12321235
source: Place<'tcx>,
@@ -1452,12 +1455,7 @@ impl<'tcx> MatchTreeSubBranch<'tcx> {
14521455
span: candidate.extra_data.span,
14531456
success_block: candidate.pre_binding_block.unwrap(),
14541457
otherwise_block: candidate.otherwise_block.unwrap(),
1455-
bindings: parent_data
1456-
.iter()
1457-
.flat_map(|d| &d.bindings)
1458-
.chain(&candidate.extra_data.bindings)
1459-
.cloned()
1460-
.collect(),
1458+
bindings: sub_branch_bindings(parent_data, &candidate.extra_data.bindings),
14611459
ascriptions: parent_data
14621460
.iter()
14631461
.flat_map(|d| &d.ascriptions)
@@ -1490,6 +1488,66 @@ impl<'tcx> MatchTreeBranch<'tcx> {
14901488
}
14911489
}
14921490

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+
14931551
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14941552
pub(crate) enum HasMatchGuard {
14951553
Yes,
@@ -2453,11 +2511,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
24532511

24542512
// Bindings for guards require some extra handling to automatically
24552513
// insert implicit references/dereferences.
2456-
self.bind_matched_candidate_for_guard(
2457-
block,
2458-
schedule_drops,
2459-
sub_branch.bindings.iter(),
2460-
);
2514+
// This always schedules storage drops, so we may need to unschedule them below.
2515+
self.bind_matched_candidate_for_guard(block, sub_branch.bindings.iter());
24612516
let guard_frame = GuardFrame {
24622517
locals: sub_branch
24632518
.bindings
@@ -2489,6 +2544,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
24892544
)
24902545
});
24912546

2547+
// If this isn't the final sub-branch being lowered, we need to unschedule drops of
2548+
// bindings and temporaries created for and by the guard. As a result, the drop order
2549+
// for the arm will correspond to the binding order of the final sub-branch lowered.
2550+
if matches!(schedule_drops, ScheduleDrops::No) {
2551+
self.clear_top_scope(arm.scope);
2552+
}
2553+
24922554
let source_info = self.source_info(guard_span);
24932555
let guard_end = self.source_info(tcx.sess.source_map().end_point(guard_span));
24942556
let guard_frame = self.guard_context.pop().unwrap();
@@ -2538,14 +2600,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
25382600
let cause = FakeReadCause::ForGuardBinding;
25392601
self.cfg.push_fake_read(post_guard_block, guard_end, cause, Place::from(local_id));
25402602
}
2541-
assert_matches!(
2542-
schedule_drops,
2543-
ScheduleDrops::Yes,
2544-
"patterns with guards must schedule drops"
2545-
);
2603+
// Only schedule drops for the last sub-branch we lower.
25462604
self.bind_matched_candidate_for_arm_body(
25472605
post_guard_block,
2548-
ScheduleDrops::Yes,
2606+
schedule_drops,
25492607
by_value_bindings,
25502608
emit_storage_live,
25512609
);
@@ -2671,7 +2729,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
26712729
fn bind_matched_candidate_for_guard<'b>(
26722730
&mut self,
26732731
block: BasicBlock,
2674-
schedule_drops: ScheduleDrops,
26752732
bindings: impl IntoIterator<Item = &'b Binding<'tcx>>,
26762733
) where
26772734
'tcx: 'b,
@@ -2690,12 +2747,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
26902747
// a reference R: &T pointing to the location matched by
26912748
// the pattern, and every occurrence of P within a guard
26922749
// denotes *R.
2750+
// Drops must be scheduled to emit `StorageDead` on the guard's failure/break branches.
26932751
let ref_for_guard = self.storage_live_binding(
26942752
block,
26952753
binding.var_id,
26962754
binding.span,
26972755
RefWithinGuard,
2698-
schedule_drops,
2756+
ScheduleDrops::Yes,
26992757
);
27002758
match binding.binding_mode.0 {
27012759
ByRef::No => {
@@ -2705,13 +2763,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
27052763
self.cfg.push_assign(block, source_info, ref_for_guard, rvalue);
27062764
}
27072765
ByRef::Yes(mutbl) => {
2708-
// The arm binding will be by reference, so eagerly create it now.
2766+
// The arm binding will be by reference, so eagerly create it now. Drops must
2767+
// be scheduled to emit `StorageDead` on the guard's failure/break branches.
27092768
let value_for_arm = self.storage_live_binding(
27102769
block,
27112770
binding.var_id,
27122771
binding.span,
27132772
OutsideGuard,
2714-
schedule_drops,
2773+
ScheduleDrops::Yes,
27152774
);
27162775

27172776
let rvalue =

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/mir-opt/match_arm_scopes.complicated_match.panic-unwind.SimplifyCfg-initial.after-ElaborateDrops.after.diff

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,11 @@
8585
_8 = &(_2.2: std::string::String);
8686
- _3 = &fake shallow (_2.0: bool);
8787
- _4 = &fake shallow (_2.1: bool);
88-
StorageLive(_12);
89-
StorageLive(_13);
90-
_13 = copy _1;
91-
- switchInt(move _13) -> [0: bb16, otherwise: bb15];
92-
+ switchInt(move _13) -> [0: bb13, otherwise: bb12];
88+
StorageLive(_9);
89+
StorageLive(_10);
90+
_10 = copy _1;
91+
- switchInt(move _10) -> [0: bb12, otherwise: bb11];
92+
+ switchInt(move _10) -> [0: bb9, otherwise: bb8];
9393
}
9494

9595
- bb9: {
@@ -100,11 +100,11 @@
100100
_8 = &(_2.2: std::string::String);
101101
- _3 = &fake shallow (_2.0: bool);
102102
- _4 = &fake shallow (_2.1: bool);
103-
StorageLive(_9);
104-
StorageLive(_10);
105-
_10 = copy _1;
106-
- switchInt(move _10) -> [0: bb12, otherwise: bb11];
107-
+ switchInt(move _10) -> [0: bb9, otherwise: bb8];
103+
StorageLive(_12);
104+
StorageLive(_13);
105+
_13 = copy _1;
106+
- switchInt(move _13) -> [0: bb16, otherwise: bb15];
107+
+ switchInt(move _13) -> [0: bb13, otherwise: bb12];
108108
}
109109

110110
- bb10: {
@@ -139,7 +139,7 @@
139139
- FakeRead(ForGuardBinding, _6);
140140
- FakeRead(ForGuardBinding, _8);
141141
StorageLive(_5);
142-
_5 = copy (_2.1: bool);
142+
_5 = copy (_2.0: bool);
143143
StorageLive(_7);
144144
_7 = move (_2.2: std::string::String);
145145
- goto -> bb10;
@@ -152,8 +152,8 @@
152152
StorageDead(_9);
153153
StorageDead(_8);
154154
StorageDead(_6);
155-
- falseEdge -> [real: bb1, imaginary: bb1];
156-
+ goto -> bb1;
155+
- falseEdge -> [real: bb3, imaginary: bb3];
156+
+ goto -> bb2;
157157
}
158158

159159
- bb15: {
@@ -181,7 +181,7 @@
181181
- FakeRead(ForGuardBinding, _6);
182182
- FakeRead(ForGuardBinding, _8);
183183
StorageLive(_5);
184-
_5 = copy (_2.0: bool);
184+
_5 = copy (_2.1: bool);
185185
StorageLive(_7);
186186
_7 = move (_2.2: std::string::String);
187187
- goto -> bb10;
@@ -194,8 +194,8 @@
194194
StorageDead(_12);
195195
StorageDead(_8);
196196
StorageDead(_6);
197-
- falseEdge -> [real: bb3, imaginary: bb3];
198-
+ goto -> bb2;
197+
- falseEdge -> [real: bb1, imaginary: bb1];
198+
+ goto -> bb1;
199199
}
200200

201201
- bb19: {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy