@@ -1598,6 +1598,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
15981598 for subcandidate in candidate. subcandidates . iter_mut ( ) {
15991599 expanded_candidates. push ( subcandidate) ;
16001600 }
1601+ // Note that the subcandidates have been added to `expanded_candidates`,
1602+ // but `candidate` itself has not. If the last candidate has more match pairs,
1603+ // they are handled separately by `test_remaining_match_pairs_after_or`.
16011604 } else {
16021605 // A candidate that doesn't start with an or-pattern has nothing to
16031606 // expand, so it is included in the post-expansion list as-is.
@@ -1613,19 +1616,28 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
16131616 expanded_candidates. as_mut_slice ( ) ,
16141617 ) ;
16151618
1616- // Simplify subcandidates and process any leftover match pairs.
1617- for candidate in candidates_to_expand {
1619+ // Postprocess subcandidates, and process any leftover match pairs.
1620+ // (Only the last candidate can possibly have more match pairs.)
1621+ debug_assert ! ( {
1622+ let mut all_except_last = candidates_to_expand. iter( ) . rev( ) . skip( 1 ) ;
1623+ all_except_last. all( |candidate| candidate. match_pairs. is_empty( ) )
1624+ } ) ;
1625+ for candidate in candidates_to_expand. iter_mut ( ) {
16181626 if !candidate. subcandidates . is_empty ( ) {
1619- self . finalize_or_candidate ( span, scrutinee_span, candidate) ;
1627+ self . merge_trivial_subcandidates ( candidate) ;
1628+ self . remove_never_subcandidates ( candidate) ;
16201629 }
16211630 }
1631+ if let Some ( last_candidate) = candidates_to_expand. last_mut ( ) {
1632+ self . test_remaining_match_pairs_after_or ( span, scrutinee_span, last_candidate) ;
1633+ }
16221634
16231635 remainder_start. and ( remaining_candidates)
16241636 }
16251637
16261638 /// Given a match-pair that corresponds to an or-pattern, expand each subpattern into a new
1627- /// subcandidate. Any candidate that has been expanded that way should be passed to
1628- /// `finalize_or_candidate` after its subcandidates have been processed .
1639+ /// subcandidate. Any candidate that has been expanded this way should also be postprocessed
1640+ /// at the end of [`Self::expand_and_match_or_candidates`] .
16291641 fn create_or_subcandidates < ' pat > (
16301642 & mut self ,
16311643 candidate : & mut Candidate < ' pat , ' tcx > ,
@@ -1642,7 +1654,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
16421654 candidate. subcandidates [ 0 ] . false_edge_start_block = candidate. false_edge_start_block ;
16431655 }
16441656
1645- /// Simplify subcandidates and process any leftover match pairs. The candidate should have been
1657+ /// Try to merge all of the subcandidates of the given candidate into one. This avoids
1658+ /// exponentially large CFGs in cases like `(1 | 2, 3 | 4, ...)`. The candidate should have been
16461659 /// expanded with `create_or_subcandidates`.
16471660 ///
16481661 /// Given a pattern `(P | Q, R | S)` we (in principle) generate a CFG like
@@ -1695,103 +1708,128 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
16951708 /// |
16961709 /// ...
16971710 /// ```
1698- fn finalize_or_candidate (
1699- & mut self ,
1700- span : Span ,
1701- scrutinee_span : Span ,
1702- candidate : & mut Candidate < ' _ , ' tcx > ,
1703- ) {
1704- if candidate . subcandidates . is_empty ( ) {
1711+ ///
1712+ /// Note that this takes place _after_ the subcandidates have participated
1713+ /// in match tree lowering.
1714+ fn merge_trivial_subcandidates ( & mut self , candidate : & mut Candidate < ' _ , ' tcx > ) {
1715+ assert ! ( ! candidate. subcandidates . is_empty ( ) ) ;
1716+ if candidate . has_guard {
1717+ // FIXME(or_patterns; matthewjasper) Don't give up if we have a guard.
17051718 return ;
17061719 }
17071720
1708- self . merge_trivial_subcandidates ( candidate) ;
1721+ // FIXME(or_patterns; matthewjasper) Try to be more aggressive here.
1722+ let can_merge = candidate. subcandidates . iter ( ) . all ( |subcandidate| {
1723+ subcandidate. subcandidates . is_empty ( ) && subcandidate. extra_data . is_empty ( )
1724+ } ) ;
1725+ if !can_merge {
1726+ return ;
1727+ }
17091728
1710- if !candidate. match_pairs . is_empty ( ) {
1711- let or_span = candidate. or_span . unwrap_or ( candidate. extra_data . span ) ;
1712- let source_info = self . source_info ( or_span) ;
1713- // If more match pairs remain, test them after each subcandidate.
1714- // We could add them to the or-candidates before the call to `test_or_pattern` but this
1715- // would make it impossible to detect simplifiable or-patterns. That would guarantee
1716- // exponentially large CFGs for cases like `(1 | 2, 3 | 4, ...)`.
1717- let mut last_otherwise = None ;
1718- candidate. visit_leaves ( |leaf_candidate| {
1719- last_otherwise = leaf_candidate. otherwise_block ;
1720- } ) ;
1721- let remaining_match_pairs = mem:: take ( & mut candidate. match_pairs ) ;
1722- candidate. visit_leaves ( |leaf_candidate| {
1723- assert ! ( leaf_candidate. match_pairs. is_empty( ) ) ;
1724- leaf_candidate. match_pairs . extend ( remaining_match_pairs. iter ( ) . cloned ( ) ) ;
1725- let or_start = leaf_candidate. pre_binding_block . unwrap ( ) ;
1726- let otherwise =
1727- self . match_candidates ( span, scrutinee_span, or_start, & mut [ leaf_candidate] ) ;
1728- // In a case like `(P | Q, R | S)`, if `P` succeeds and `R | S` fails, we know `(Q,
1729- // R | S)` will fail too. If there is no guard, we skip testing of `Q` by branching
1730- // directly to `last_otherwise`. If there is a guard,
1731- // `leaf_candidate.otherwise_block` can be reached by guard failure as well, so we
1732- // can't skip `Q`.
1733- let or_otherwise = if leaf_candidate. has_guard {
1734- leaf_candidate. otherwise_block . unwrap ( )
1735- } else {
1736- last_otherwise. unwrap ( )
1737- } ;
1738- self . cfg . goto ( otherwise, source_info, or_otherwise) ;
1739- } ) ;
1729+ let mut last_otherwise = None ;
1730+ let shared_pre_binding_block = self . cfg . start_new_block ( ) ;
1731+ // This candidate is about to become a leaf, so unset `or_span`.
1732+ let or_span = candidate. or_span . take ( ) . unwrap ( ) ;
1733+ let source_info = self . source_info ( or_span) ;
1734+
1735+ if candidate. false_edge_start_block . is_none ( ) {
1736+ candidate. false_edge_start_block = candidate. subcandidates [ 0 ] . false_edge_start_block ;
1737+ }
1738+
1739+ // Remove the (known-trivial) subcandidates from the candidate tree,
1740+ // so that they aren't visible after match tree lowering, and wire them
1741+ // all to join up at a single shared pre-binding block.
1742+ // (Note that the subcandidates have already had their part of the match
1743+ // tree lowered by this point, which is why we can add a goto to them.)
1744+ for subcandidate in mem:: take ( & mut candidate. subcandidates ) {
1745+ let subcandidate_block = subcandidate. pre_binding_block . unwrap ( ) ;
1746+ self . cfg . goto ( subcandidate_block, source_info, shared_pre_binding_block) ;
1747+ last_otherwise = subcandidate. otherwise_block ;
17401748 }
1749+ candidate. pre_binding_block = Some ( shared_pre_binding_block) ;
1750+ assert ! ( last_otherwise. is_some( ) ) ;
1751+ candidate. otherwise_block = last_otherwise;
17411752 }
17421753
1743- /// Try to merge all of the subcandidates of the given candidate into one. This avoids
1744- /// exponentially large CFGs in cases like `(1 | 2, 3 | 4, ...)`. The candidate should have been
1745- /// expanded with `create_or_subcandidates`.
1746- fn merge_trivial_subcandidates ( & mut self , candidate : & mut Candidate < ' _ , ' tcx > ) {
1747- if candidate. subcandidates . is_empty ( ) || candidate. has_guard {
1748- // FIXME(or_patterns; matthewjasper) Don't give up if we have a guard.
1754+ /// Never subcandidates may have a set of bindings inconsistent with their siblings,
1755+ /// which would break later code. So we filter them out. Note that we can't filter out
1756+ /// top-level candidates this way.
1757+ fn remove_never_subcandidates ( & mut self , candidate : & mut Candidate < ' _ , ' tcx > ) {
1758+ if candidate. subcandidates . is_empty ( ) {
17491759 return ;
17501760 }
17511761
1752- // FIXME(or_patterns; matthewjasper) Try to be more aggressive here.
1753- let can_merge = candidate. subcandidates . iter ( ) . all ( |subcandidate| {
1754- subcandidate. subcandidates . is_empty ( ) && subcandidate. extra_data . is_empty ( )
1755- } ) ;
1756- if can_merge {
1757- let mut last_otherwise = None ;
1758- let any_matches = self . cfg . start_new_block ( ) ;
1759- let or_span = candidate. or_span . take ( ) . unwrap ( ) ;
1760- let source_info = self . source_info ( or_span) ;
1761- if candidate. false_edge_start_block . is_none ( ) {
1762- candidate. false_edge_start_block =
1763- candidate. subcandidates [ 0 ] . false_edge_start_block ;
1764- }
1765- for subcandidate in mem:: take ( & mut candidate. subcandidates ) {
1766- let or_block = subcandidate. pre_binding_block . unwrap ( ) ;
1767- self . cfg . goto ( or_block, source_info, any_matches) ;
1768- last_otherwise = subcandidate. otherwise_block ;
1769- }
1770- candidate. pre_binding_block = Some ( any_matches) ;
1771- assert ! ( last_otherwise. is_some( ) ) ;
1772- candidate. otherwise_block = last_otherwise;
1773- } else {
1774- // Never subcandidates may have a set of bindings inconsistent with their siblings,
1775- // which would break later code. So we filter them out. Note that we can't filter out
1776- // top-level candidates this way.
1777- candidate. subcandidates . retain_mut ( |candidate| {
1778- if candidate. extra_data . is_never {
1779- candidate. visit_leaves ( |subcandidate| {
1780- let block = subcandidate. pre_binding_block . unwrap ( ) ;
1781- // That block is already unreachable but needs a terminator to make the MIR well-formed.
1782- let source_info = self . source_info ( subcandidate. extra_data . span ) ;
1783- self . cfg . terminate ( block, source_info, TerminatorKind :: Unreachable ) ;
1784- } ) ;
1785- false
1786- } else {
1787- true
1788- }
1789- } ) ;
1790- if candidate. subcandidates . is_empty ( ) {
1791- // If `candidate` has become a leaf candidate, ensure it has a `pre_binding_block`.
1792- candidate. pre_binding_block = Some ( self . cfg . start_new_block ( ) ) ;
1762+ candidate. subcandidates . retain_mut ( |candidate| {
1763+ if candidate. extra_data . is_never {
1764+ candidate. visit_leaves ( |subcandidate| {
1765+ let block = subcandidate. pre_binding_block . unwrap ( ) ;
1766+ // That block is already unreachable but needs a terminator to make the MIR well-formed.
1767+ let source_info = self . source_info ( subcandidate. extra_data . span ) ;
1768+ self . cfg . terminate ( block, source_info, TerminatorKind :: Unreachable ) ;
1769+ } ) ;
1770+ false
1771+ } else {
1772+ true
17931773 }
1774+ } ) ;
1775+ if candidate. subcandidates . is_empty ( ) {
1776+ // If `candidate` has become a leaf candidate, ensure it has a `pre_binding_block`.
1777+ candidate. pre_binding_block = Some ( self . cfg . start_new_block ( ) ) ;
1778+ }
1779+ }
1780+
1781+ /// If more match pairs remain, test them after each subcandidate.
1782+ /// We could have added them to the or-candidates during or-pattern expansion, but that
1783+ /// would make it impossible to detect simplifiable or-patterns. That would guarantee
1784+ /// exponentially large CFGs for cases like `(1 | 2, 3 | 4, ...)`.
1785+ fn test_remaining_match_pairs_after_or (
1786+ & mut self ,
1787+ span : Span ,
1788+ scrutinee_span : Span ,
1789+ candidate : & mut Candidate < ' _ , ' tcx > ,
1790+ ) {
1791+ if candidate. match_pairs . is_empty ( ) {
1792+ return ;
17941793 }
1794+
1795+ let or_span = candidate. or_span . unwrap_or ( candidate. extra_data . span ) ;
1796+ let source_info = self . source_info ( or_span) ;
1797+ let mut last_otherwise = None ;
1798+ candidate. visit_leaves ( |leaf_candidate| {
1799+ last_otherwise = leaf_candidate. otherwise_block ;
1800+ } ) ;
1801+
1802+ let remaining_match_pairs = mem:: take ( & mut candidate. match_pairs ) ;
1803+ // We're testing match pairs that remained after an `Or`, so the remaining
1804+ // pairs should all be `Or` too, due to the sorting invariant.
1805+ debug_assert ! (
1806+ remaining_match_pairs
1807+ . iter( )
1808+ . all( |match_pair| matches!( match_pair. test_case, TestCase :: Or { .. } ) )
1809+ ) ;
1810+
1811+ candidate. visit_leaves ( |leaf_candidate| {
1812+ // At this point the leaf's own match pairs have all been lowered
1813+ // and removed, so `extend` and assignment are equivalent,
1814+ // but extending can also recycle any existing vector capacity.
1815+ assert ! ( leaf_candidate. match_pairs. is_empty( ) ) ;
1816+ leaf_candidate. match_pairs . extend ( remaining_match_pairs. iter ( ) . cloned ( ) ) ;
1817+
1818+ let or_start = leaf_candidate. pre_binding_block . unwrap ( ) ;
1819+ let otherwise =
1820+ self . match_candidates ( span, scrutinee_span, or_start, & mut [ leaf_candidate] ) ;
1821+ // In a case like `(P | Q, R | S)`, if `P` succeeds and `R | S` fails, we know `(Q,
1822+ // R | S)` will fail too. If there is no guard, we skip testing of `Q` by branching
1823+ // directly to `last_otherwise`. If there is a guard,
1824+ // `leaf_candidate.otherwise_block` can be reached by guard failure as well, so we
1825+ // can't skip `Q`.
1826+ let or_otherwise = if leaf_candidate. has_guard {
1827+ leaf_candidate. otherwise_block . unwrap ( )
1828+ } else {
1829+ last_otherwise. unwrap ( )
1830+ } ;
1831+ self . cfg . goto ( otherwise, source_info, or_otherwise) ;
1832+ } ) ;
17951833 }
17961834
17971835 /// Pick a test to run. Which test doesn't matter as long as it is guaranteed to fully match at
0 commit comments