Rollup merge of #127917 - Zalathar:after-or, r=Nadrieril
match lowering: Split `finalize_or_candidate` into more coherent methods I noticed that `finalize_or_candidate` was responsible for several different postprocessing tasks, making it difficult to understand. This PR aims to clean up some of the confusion by: - Extracting `remove_never_subcandidates` from `merge_trivial_subcandidates` - Extracting `test_remaining_match_pairs_after_or` from `finalize_or_candidate` - Taking what remains of `finalize_or_candidate`, and inlining it into its caller --- Reviewing individual commits and ignoring whitespace is recommended. Most of the large-looking changes are just moving existing code around, mostly unaltered. r? ``@Nadrieril``
This commit is contained in:
commit
d846e9252c
@ -1598,6 +1598,9 @@ fn expand_and_match_or_candidates<'pat, 'b, 'c>(
|
|||||||
for subcandidate in candidate.subcandidates.iter_mut() {
|
for subcandidate in candidate.subcandidates.iter_mut() {
|
||||||
expanded_candidates.push(subcandidate);
|
expanded_candidates.push(subcandidate);
|
||||||
}
|
}
|
||||||
|
// Note that the subcandidates have been added to `expanded_candidates`,
|
||||||
|
// but `candidate` itself has not. If the last candidate has more match pairs,
|
||||||
|
// they are handled separately by `test_remaining_match_pairs_after_or`.
|
||||||
} else {
|
} else {
|
||||||
// A candidate that doesn't start with an or-pattern has nothing to
|
// A candidate that doesn't start with an or-pattern has nothing to
|
||||||
// expand, so it is included in the post-expansion list as-is.
|
// expand, so it is included in the post-expansion list as-is.
|
||||||
@ -1613,19 +1616,28 @@ fn expand_and_match_or_candidates<'pat, 'b, 'c>(
|
|||||||
expanded_candidates.as_mut_slice(),
|
expanded_candidates.as_mut_slice(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Simplify subcandidates and process any leftover match pairs.
|
// Postprocess subcandidates, and process any leftover match pairs.
|
||||||
for candidate in candidates_to_expand {
|
// (Only the last candidate can possibly have more match pairs.)
|
||||||
|
debug_assert!({
|
||||||
|
let mut all_except_last = candidates_to_expand.iter().rev().skip(1);
|
||||||
|
all_except_last.all(|candidate| candidate.match_pairs.is_empty())
|
||||||
|
});
|
||||||
|
for candidate in candidates_to_expand.iter_mut() {
|
||||||
if !candidate.subcandidates.is_empty() {
|
if !candidate.subcandidates.is_empty() {
|
||||||
self.finalize_or_candidate(span, scrutinee_span, candidate);
|
self.merge_trivial_subcandidates(candidate);
|
||||||
|
self.remove_never_subcandidates(candidate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(last_candidate) = candidates_to_expand.last_mut() {
|
||||||
|
self.test_remaining_match_pairs_after_or(span, scrutinee_span, last_candidate);
|
||||||
|
}
|
||||||
|
|
||||||
remainder_start.and(remaining_candidates)
|
remainder_start.and(remaining_candidates)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given a match-pair that corresponds to an or-pattern, expand each subpattern into a new
|
/// Given a match-pair that corresponds to an or-pattern, expand each subpattern into a new
|
||||||
/// subcandidate. Any candidate that has been expanded that way should be passed to
|
/// subcandidate. Any candidate that has been expanded this way should also be postprocessed
|
||||||
/// `finalize_or_candidate` after its subcandidates have been processed.
|
/// at the end of [`Self::expand_and_match_or_candidates`].
|
||||||
fn create_or_subcandidates<'pat>(
|
fn create_or_subcandidates<'pat>(
|
||||||
&mut self,
|
&mut self,
|
||||||
candidate: &mut Candidate<'pat, 'tcx>,
|
candidate: &mut Candidate<'pat, 'tcx>,
|
||||||
@ -1642,7 +1654,8 @@ fn create_or_subcandidates<'pat>(
|
|||||||
candidate.subcandidates[0].false_edge_start_block = candidate.false_edge_start_block;
|
candidate.subcandidates[0].false_edge_start_block = candidate.false_edge_start_block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simplify subcandidates and process any leftover match pairs. The candidate should have been
|
/// Try to merge all of the subcandidates of the given candidate into one. This avoids
|
||||||
|
/// exponentially large CFGs in cases like `(1 | 2, 3 | 4, ...)`. The candidate should have been
|
||||||
/// expanded with `create_or_subcandidates`.
|
/// expanded with `create_or_subcandidates`.
|
||||||
///
|
///
|
||||||
/// Given a pattern `(P | Q, R | S)` we (in principle) generate a CFG like
|
/// Given a pattern `(P | Q, R | S)` we (in principle) generate a CFG like
|
||||||
@ -1695,56 +1708,12 @@ fn create_or_subcandidates<'pat>(
|
|||||||
/// |
|
/// |
|
||||||
/// ...
|
/// ...
|
||||||
/// ```
|
/// ```
|
||||||
fn finalize_or_candidate(
|
///
|
||||||
&mut self,
|
/// Note that this takes place _after_ the subcandidates have participated
|
||||||
span: Span,
|
/// in match tree lowering.
|
||||||
scrutinee_span: Span,
|
|
||||||
candidate: &mut Candidate<'_, 'tcx>,
|
|
||||||
) {
|
|
||||||
if candidate.subcandidates.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.merge_trivial_subcandidates(candidate);
|
|
||||||
|
|
||||||
if !candidate.match_pairs.is_empty() {
|
|
||||||
let or_span = candidate.or_span.unwrap_or(candidate.extra_data.span);
|
|
||||||
let source_info = self.source_info(or_span);
|
|
||||||
// If more match pairs remain, test them after each subcandidate.
|
|
||||||
// We could add them to the or-candidates before the call to `test_or_pattern` but this
|
|
||||||
// would make it impossible to detect simplifiable or-patterns. That would guarantee
|
|
||||||
// exponentially large CFGs for cases like `(1 | 2, 3 | 4, ...)`.
|
|
||||||
let mut last_otherwise = None;
|
|
||||||
candidate.visit_leaves(|leaf_candidate| {
|
|
||||||
last_otherwise = leaf_candidate.otherwise_block;
|
|
||||||
});
|
|
||||||
let remaining_match_pairs = mem::take(&mut candidate.match_pairs);
|
|
||||||
candidate.visit_leaves(|leaf_candidate| {
|
|
||||||
assert!(leaf_candidate.match_pairs.is_empty());
|
|
||||||
leaf_candidate.match_pairs.extend(remaining_match_pairs.iter().cloned());
|
|
||||||
let or_start = leaf_candidate.pre_binding_block.unwrap();
|
|
||||||
let otherwise =
|
|
||||||
self.match_candidates(span, scrutinee_span, or_start, &mut [leaf_candidate]);
|
|
||||||
// In a case like `(P | Q, R | S)`, if `P` succeeds and `R | S` fails, we know `(Q,
|
|
||||||
// R | S)` will fail too. If there is no guard, we skip testing of `Q` by branching
|
|
||||||
// directly to `last_otherwise`. If there is a guard,
|
|
||||||
// `leaf_candidate.otherwise_block` can be reached by guard failure as well, so we
|
|
||||||
// can't skip `Q`.
|
|
||||||
let or_otherwise = if leaf_candidate.has_guard {
|
|
||||||
leaf_candidate.otherwise_block.unwrap()
|
|
||||||
} else {
|
|
||||||
last_otherwise.unwrap()
|
|
||||||
};
|
|
||||||
self.cfg.goto(otherwise, source_info, or_otherwise);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to merge all of the subcandidates of the given candidate into one. This avoids
|
|
||||||
/// exponentially large CFGs in cases like `(1 | 2, 3 | 4, ...)`. The candidate should have been
|
|
||||||
/// expanded with `create_or_subcandidates`.
|
|
||||||
fn merge_trivial_subcandidates(&mut self, candidate: &mut Candidate<'_, 'tcx>) {
|
fn merge_trivial_subcandidates(&mut self, candidate: &mut Candidate<'_, 'tcx>) {
|
||||||
if candidate.subcandidates.is_empty() || candidate.has_guard {
|
assert!(!candidate.subcandidates.is_empty());
|
||||||
|
if candidate.has_guard {
|
||||||
// FIXME(or_patterns; matthewjasper) Don't give up if we have a guard.
|
// FIXME(or_patterns; matthewjasper) Don't give up if we have a guard.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1753,27 +1722,43 @@ fn merge_trivial_subcandidates(&mut self, candidate: &mut Candidate<'_, 'tcx>) {
|
|||||||
let can_merge = candidate.subcandidates.iter().all(|subcandidate| {
|
let can_merge = candidate.subcandidates.iter().all(|subcandidate| {
|
||||||
subcandidate.subcandidates.is_empty() && subcandidate.extra_data.is_empty()
|
subcandidate.subcandidates.is_empty() && subcandidate.extra_data.is_empty()
|
||||||
});
|
});
|
||||||
if can_merge {
|
if !can_merge {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mut last_otherwise = None;
|
let mut last_otherwise = None;
|
||||||
let any_matches = self.cfg.start_new_block();
|
let shared_pre_binding_block = self.cfg.start_new_block();
|
||||||
|
// This candidate is about to become a leaf, so unset `or_span`.
|
||||||
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() {
|
if candidate.false_edge_start_block.is_none() {
|
||||||
candidate.false_edge_start_block =
|
candidate.false_edge_start_block = candidate.subcandidates[0].false_edge_start_block;
|
||||||
candidate.subcandidates[0].false_edge_start_block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the (known-trivial) subcandidates from the candidate tree,
|
||||||
|
// so that they aren't visible after match tree lowering, and wire them
|
||||||
|
// all to join up at a single shared pre-binding block.
|
||||||
|
// (Note that the subcandidates have already had their part of the match
|
||||||
|
// tree lowered by this point, which is why we can add a goto to them.)
|
||||||
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 subcandidate_block = subcandidate.pre_binding_block.unwrap();
|
||||||
self.cfg.goto(or_block, source_info, any_matches);
|
self.cfg.goto(subcandidate_block, source_info, shared_pre_binding_block);
|
||||||
last_otherwise = subcandidate.otherwise_block;
|
last_otherwise = subcandidate.otherwise_block;
|
||||||
}
|
}
|
||||||
candidate.pre_binding_block = Some(any_matches);
|
candidate.pre_binding_block = Some(shared_pre_binding_block);
|
||||||
assert!(last_otherwise.is_some());
|
assert!(last_otherwise.is_some());
|
||||||
candidate.otherwise_block = last_otherwise;
|
candidate.otherwise_block = last_otherwise;
|
||||||
} else {
|
}
|
||||||
// Never subcandidates may have a set of bindings inconsistent with their siblings,
|
|
||||||
// which would break later code. So we filter them out. Note that we can't filter out
|
/// Never subcandidates may have a set of bindings inconsistent with their siblings,
|
||||||
// top-level candidates this way.
|
/// which would break later code. So we filter them out. Note that we can't filter out
|
||||||
|
/// top-level candidates this way.
|
||||||
|
fn remove_never_subcandidates(&mut self, candidate: &mut Candidate<'_, 'tcx>) {
|
||||||
|
if candidate.subcandidates.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
candidate.subcandidates.retain_mut(|candidate| {
|
candidate.subcandidates.retain_mut(|candidate| {
|
||||||
if candidate.extra_data.is_never {
|
if candidate.extra_data.is_never {
|
||||||
candidate.visit_leaves(|subcandidate| {
|
candidate.visit_leaves(|subcandidate| {
|
||||||
@ -1792,6 +1777,59 @@ fn merge_trivial_subcandidates(&mut self, candidate: &mut Candidate<'_, 'tcx>) {
|
|||||||
candidate.pre_binding_block = Some(self.cfg.start_new_block());
|
candidate.pre_binding_block = Some(self.cfg.start_new_block());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If more match pairs remain, test them after each subcandidate.
|
||||||
|
/// We could have added them to the or-candidates during or-pattern expansion, but that
|
||||||
|
/// would make it impossible to detect simplifiable or-patterns. That would guarantee
|
||||||
|
/// exponentially large CFGs for cases like `(1 | 2, 3 | 4, ...)`.
|
||||||
|
fn test_remaining_match_pairs_after_or(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
scrutinee_span: Span,
|
||||||
|
candidate: &mut Candidate<'_, 'tcx>,
|
||||||
|
) {
|
||||||
|
if candidate.match_pairs.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let or_span = candidate.or_span.unwrap_or(candidate.extra_data.span);
|
||||||
|
let source_info = self.source_info(or_span);
|
||||||
|
let mut last_otherwise = None;
|
||||||
|
candidate.visit_leaves(|leaf_candidate| {
|
||||||
|
last_otherwise = leaf_candidate.otherwise_block;
|
||||||
|
});
|
||||||
|
|
||||||
|
let remaining_match_pairs = mem::take(&mut candidate.match_pairs);
|
||||||
|
// We're testing match pairs that remained after an `Or`, so the remaining
|
||||||
|
// pairs should all be `Or` too, due to the sorting invariant.
|
||||||
|
debug_assert!(
|
||||||
|
remaining_match_pairs
|
||||||
|
.iter()
|
||||||
|
.all(|match_pair| matches!(match_pair.test_case, TestCase::Or { .. }))
|
||||||
|
);
|
||||||
|
|
||||||
|
candidate.visit_leaves(|leaf_candidate| {
|
||||||
|
// At this point the leaf's own match pairs have all been lowered
|
||||||
|
// and removed, so `extend` and assignment are equivalent,
|
||||||
|
// but extending can also recycle any existing vector capacity.
|
||||||
|
assert!(leaf_candidate.match_pairs.is_empty());
|
||||||
|
leaf_candidate.match_pairs.extend(remaining_match_pairs.iter().cloned());
|
||||||
|
|
||||||
|
let or_start = leaf_candidate.pre_binding_block.unwrap();
|
||||||
|
let otherwise =
|
||||||
|
self.match_candidates(span, scrutinee_span, or_start, &mut [leaf_candidate]);
|
||||||
|
// In a case like `(P | Q, R | S)`, if `P` succeeds and `R | S` fails, we know `(Q,
|
||||||
|
// R | S)` will fail too. If there is no guard, we skip testing of `Q` by branching
|
||||||
|
// directly to `last_otherwise`. If there is a guard,
|
||||||
|
// `leaf_candidate.otherwise_block` can be reached by guard failure as well, so we
|
||||||
|
// can't skip `Q`.
|
||||||
|
let or_otherwise = if leaf_candidate.has_guard {
|
||||||
|
leaf_candidate.otherwise_block.unwrap()
|
||||||
|
} else {
|
||||||
|
last_otherwise.unwrap()
|
||||||
|
};
|
||||||
|
self.cfg.goto(otherwise, source_info, or_otherwise);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pick a test to run. Which test doesn't matter as long as it is guaranteed to fully match at
|
/// Pick a test to run. Which test doesn't matter as long as it is guaranteed to fully match at
|
||||||
|
Loading…
Reference in New Issue
Block a user