@@ -1598,6 +1598,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
1598
1598
for subcandidate in candidate. subcandidates . iter_mut ( ) {
1599
1599
expanded_candidates. push ( subcandidate) ;
1600
1600
}
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`.
1601
1604
} else {
1602
1605
// A candidate that doesn't start with an or-pattern has nothing to
1603
1606
// expand, so it is included in the post-expansion list as-is.
@@ -1613,19 +1616,28 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
1613
1616
expanded_candidates. as_mut_slice ( ) ,
1614
1617
) ;
1615
1618
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 ( ) {
1618
1626
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) ;
1620
1629
}
1621
1630
}
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
+ }
1622
1634
1623
1635
remainder_start. and ( remaining_candidates)
1624
1636
}
1625
1637
1626
1638
/// 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`] .
1629
1641
fn create_or_subcandidates < ' pat > (
1630
1642
& mut self ,
1631
1643
candidate : & mut Candidate < ' pat , ' tcx > ,
@@ -1642,7 +1654,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
1642
1654
candidate. subcandidates [ 0 ] . false_edge_start_block = candidate. false_edge_start_block ;
1643
1655
}
1644
1656
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
1646
1659
/// expanded with `create_or_subcandidates`.
1647
1660
///
1648
1661
/// Given a pattern `(P | Q, R | S)` we (in principle) generate a CFG like
@@ -1695,103 +1708,128 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
1695
1708
/// |
1696
1709
/// ...
1697
1710
/// ```
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.
1705
1718
return ;
1706
1719
}
1707
1720
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
+ }
1709
1728
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 ;
1740
1748
}
1749
+ candidate. pre_binding_block = Some ( shared_pre_binding_block) ;
1750
+ assert ! ( last_otherwise. is_some( ) ) ;
1751
+ candidate. otherwise_block = last_otherwise;
1741
1752
}
1742
1753
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 ( ) {
1749
1759
return ;
1750
1760
}
1751
1761
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
1793
1773
}
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 ;
1794
1793
}
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
+ } ) ;
1795
1833
}
1796
1834
1797
1835
/// Pick a test to run. Which test doesn't matter as long as it is guaranteed to fully match at
0 commit comments