rustc_mir_build/builder/matches/
buckets.rs

1use std::cmp::Ordering;
2
3use rustc_data_structures::fx::FxIndexMap;
4use rustc_middle::mir::{BinOp, Place};
5use rustc_middle::span_bug;
6use tracing::debug;
7
8use crate::builder::Builder;
9use crate::builder::matches::test::is_switch_ty;
10use crate::builder::matches::{Candidate, Test, TestBranch, TestKind, TestableCase};
11
12/// Output of [`Builder::partition_candidates_into_buckets`].
13pub(crate) struct PartitionedCandidates<'tcx, 'b, 'c> {
14    /// For each possible outcome of the test, the candidates that are matched in that outcome.
15    pub(crate) target_candidates: FxIndexMap<TestBranch<'tcx>, Vec<&'b mut Candidate<'tcx>>>,
16    /// The remaining candidates that weren't associated with any test outcome.
17    pub(crate) remaining_candidates: &'b mut [&'c mut Candidate<'tcx>],
18}
19
20impl<'a, 'tcx> Builder<'a, 'tcx> {
21    /// Given a test, we partition the input candidates into several buckets.
22    /// If a candidate matches in exactly one of the branches of `test`
23    /// (and no other branches), we put it into the corresponding bucket.
24    /// If it could match in more than one of the branches of `test`, the test
25    /// doesn't usefully apply to it, and we stop partitioning candidates.
26    ///
27    /// Importantly, we also **mutate** the branched candidates to remove match pairs
28    /// that are entailed by the outcome of the test, and add any sub-pairs of the
29    /// removed pairs.
30    ///
31    /// For example:
32    /// ```
33    /// # let (x, y, z) = (true, true, true);
34    /// match (x, y, z) {
35    ///     (true , _    , true ) => true,  // (0)
36    ///     (false, false, _    ) => false, // (1)
37    ///     (_    , true , _    ) => true,  // (2)
38    ///     (true , _    , false) => false, // (3)
39    /// }
40    /// # ;
41    /// ```
42    ///
43    /// Assume we are testing on `x`. Conceptually, there are 2 overlapping candidate sets:
44    /// - If the outcome is that `x` is true, candidates {0, 2, 3} are possible
45    /// - If the outcome is that `x` is false, candidates {1, 2} are possible
46    ///
47    /// Following our algorithm:
48    /// - Candidate 0 is bucketed into outcome `x == true`
49    /// - Candidate 1 is bucketed into outcome `x == false`
50    /// - Candidate 2 remains unbucketed, because testing `x` has no effect on it
51    /// - Candidate 3 remains unbucketed, because a previous candidate (2) was unbucketed
52    ///   - This helps preserve the illusion that candidates are tested "in order"
53    ///
54    /// The bucketed candidates are mutated to remove entailed match pairs:
55    /// - candidate 0 becomes `[z @ true]` since we know that `x` was `true`;
56    /// - candidate 1 becomes `[y @ false]` since we know that `x` was `false`.
57    pub(super) fn partition_candidates_into_buckets<'b, 'c>(
58        &mut self,
59        match_place: Place<'tcx>,
60        test: &Test<'tcx>,
61        mut candidates: &'b mut [&'c mut Candidate<'tcx>],
62    ) -> PartitionedCandidates<'tcx, 'b, 'c> {
63        // For each of the possible outcomes, collect a vector of candidates that apply if the test
64        // has that particular outcome.
65        let mut target_candidates: FxIndexMap<_, Vec<&mut Candidate<'_>>> = Default::default();
66
67        let total_candidate_count = candidates.len();
68
69        // Partition the candidates into the appropriate vector in `target_candidates`.
70        // Note that at some point we may encounter a candidate where the test is not relevant;
71        // at that point, we stop partitioning.
72        while let Some(candidate) = candidates.first_mut() {
73            let Some(branch) =
74                self.choose_bucket_for_candidate(match_place, test, candidate, &target_candidates)
75            else {
76                break;
77            };
78            let (candidate, rest) = candidates.split_first_mut().unwrap();
79            target_candidates.entry(branch).or_insert_with(Vec::new).push(candidate);
80            candidates = rest;
81        }
82
83        // At least the first candidate ought to be tested
84        assert!(
85            total_candidate_count > candidates.len(),
86            "{total_candidate_count}, {candidates:#?}"
87        );
88        debug!("tested_candidates: {}", total_candidate_count - candidates.len());
89        debug!("untested_candidates: {}", candidates.len());
90
91        PartitionedCandidates { target_candidates, remaining_candidates: candidates }
92    }
93
94    /// Given that we are performing `test` against `test_place`, this job
95    /// sorts out what the status of `candidate` will be after the test. See
96    /// `test_candidates` for the usage of this function. The candidate may
97    /// be modified to update its `match_pairs`.
98    ///
99    /// So, for example, if this candidate is `x @ Some(P0)` and the `Test` is
100    /// a variant test, then we would modify the candidate to be `(x as
101    /// Option).0 @ P0` and return the index corresponding to the variant
102    /// `Some`.
103    ///
104    /// However, in some cases, the test may just not be relevant to candidate.
105    /// For example, suppose we are testing whether `foo.x == 22`, but in one
106    /// match arm we have `Foo { x: _, ... }`... in that case, the test for
107    /// the value of `x` has no particular relevance to this candidate. In
108    /// such cases, this function just returns None without doing anything.
109    /// This is used by the overall `match_candidates` algorithm to structure
110    /// the match as a whole. See `match_candidates` for more details.
111    ///
112    /// FIXME(#29623). In some cases, we have some tricky choices to make. for
113    /// example, if we are testing that `x == 22`, but the candidate is `x @
114    /// 13..55`, what should we do? In the event that the test is true, we know
115    /// that the candidate applies, but in the event of false, we don't know
116    /// that it *doesn't* apply. For now, we return false, indicate that the
117    /// test does not apply to this candidate, but it might be we can get
118    /// tighter match code if we do something a bit different.
119    fn choose_bucket_for_candidate(
120        &mut self,
121        test_place: Place<'tcx>,
122        test: &Test<'tcx>,
123        candidate: &mut Candidate<'tcx>,
124        // Other candidates that have already been partitioned into a bucket for this test, if any
125        prior_candidates: &FxIndexMap<TestBranch<'tcx>, Vec<&mut Candidate<'tcx>>>,
126    ) -> Option<TestBranch<'tcx>> {
127        // Find the match_pair for this place (if any). At present,
128        // afaik, there can be at most one. (In the future, if we
129        // adopted a more general `@` operator, there might be more
130        // than one, but it'd be very unusual to have two sides that
131        // both require tests; you'd expect one side to be simplified
132        // away.)
133        let (match_pair_index, match_pair) = candidate
134            .match_pairs
135            .iter()
136            .enumerate()
137            .find(|&(_, mp)| mp.place == Some(test_place))?;
138
139        // If true, the match pair is completely entailed by its corresponding test
140        // branch, so it can be removed. If false, the match pair is _compatible_
141        // with its test branch, but still needs a more specific test.
142        let fully_matched;
143        let ret = match (&test.kind, &match_pair.testable_case) {
144            // If we are performing a variant switch, then this
145            // informs variant patterns, but nothing else.
146            (
147                &TestKind::Switch { adt_def: tested_adt_def },
148                &TestableCase::Variant { adt_def, variant_index },
149            ) => {
150                assert_eq!(adt_def, tested_adt_def);
151                fully_matched = true;
152                Some(TestBranch::Variant(variant_index))
153            }
154
155            // If we are performing a switch over integers, then this informs integer
156            // equality, but nothing else.
157            //
158            // FIXME(#29623) we could use PatKind::Range to rule
159            // things out here, in some cases.
160            //
161            // FIXME(Zalathar): Is the `is_switch_ty` test unnecessary?
162            (TestKind::SwitchInt, &TestableCase::Constant { value })
163                if is_switch_ty(match_pair.pattern_ty) =>
164            {
165                // An important invariant of candidate bucketing is that a candidate
166                // must not match in multiple branches. For `SwitchInt` tests, adding
167                // a new value might invalidate that property for range patterns that
168                // have already been partitioned into the failure arm, so we must take care
169                // not to add such values here.
170                let is_covering_range = |testable_case: &TestableCase<'tcx>| {
171                    testable_case.as_range().is_some_and(|range| {
172                        matches!(range.contains(value, self.tcx), None | Some(true))
173                    })
174                };
175                let is_conflicting_candidate = |candidate: &&mut Candidate<'tcx>| {
176                    candidate.match_pairs.iter().any(|mp| {
177                        mp.place == Some(test_place) && is_covering_range(&mp.testable_case)
178                    })
179                };
180                if prior_candidates
181                    .get(&TestBranch::Failure)
182                    .is_some_and(|candidates| candidates.iter().any(is_conflicting_candidate))
183                {
184                    fully_matched = false;
185                    None
186                } else {
187                    fully_matched = true;
188                    Some(TestBranch::Constant(value))
189                }
190            }
191            (TestKind::SwitchInt, TestableCase::Range(range)) => {
192                // When performing a `SwitchInt` test, a range pattern can be
193                // sorted into the failure arm if it doesn't contain _any_ of
194                // the values being tested. (This restricts what values can be
195                // added to the test by subsequent candidates.)
196                fully_matched = false;
197                let not_contained = prior_candidates
198                    .keys()
199                    .filter_map(|br| br.as_constant())
200                    .all(|val| matches!(range.contains(val, self.tcx), Some(false)));
201
202                not_contained.then(|| {
203                    // No switch values are contained in the pattern range,
204                    // so the pattern can be matched only if this test fails.
205                    TestBranch::Failure
206                })
207            }
208
209            (TestKind::If, TestableCase::Constant { value }) => {
210                fully_matched = true;
211                let value = value.try_to_bool().unwrap_or_else(|| {
212                    span_bug!(test.span, "expected boolean value but got {value:?}")
213                });
214                Some(if value { TestBranch::Success } else { TestBranch::Failure })
215            }
216
217            (
218                &TestKind::Len { len: test_len, op: BinOp::Eq },
219                &TestableCase::Slice { len, variable_length },
220            ) => {
221                match (test_len.cmp(&(len as u64)), variable_length) {
222                    (Ordering::Equal, false) => {
223                        // on true, min_len = len = $actual_length,
224                        // on false, len != $actual_length
225                        fully_matched = true;
226                        Some(TestBranch::Success)
227                    }
228                    (Ordering::Less, _) => {
229                        // test_len < pat_len. If $actual_len = test_len,
230                        // then $actual_len < pat_len and we don't have
231                        // enough elements.
232                        fully_matched = false;
233                        Some(TestBranch::Failure)
234                    }
235                    (Ordering::Equal | Ordering::Greater, true) => {
236                        // This can match both if $actual_len = test_len >= pat_len,
237                        // and if $actual_len > test_len. We can't advance.
238                        fully_matched = false;
239                        None
240                    }
241                    (Ordering::Greater, false) => {
242                        // test_len != pat_len, so if $actual_len = test_len, then
243                        // $actual_len != pat_len.
244                        fully_matched = false;
245                        Some(TestBranch::Failure)
246                    }
247                }
248            }
249            (
250                &TestKind::Len { len: test_len, op: BinOp::Ge },
251                &TestableCase::Slice { len, variable_length },
252            ) => {
253                // the test is `$actual_len >= test_len`
254                match (test_len.cmp(&(len as u64)), variable_length) {
255                    (Ordering::Equal, true) => {
256                        // $actual_len >= test_len = pat_len,
257                        // so we can match.
258                        fully_matched = true;
259                        Some(TestBranch::Success)
260                    }
261                    (Ordering::Less, _) | (Ordering::Equal, false) => {
262                        // test_len <= pat_len. If $actual_len < test_len,
263                        // then it is also < pat_len, so the test passing is
264                        // necessary (but insufficient).
265                        fully_matched = false;
266                        Some(TestBranch::Success)
267                    }
268                    (Ordering::Greater, false) => {
269                        // test_len > pat_len. If $actual_len >= test_len > pat_len,
270                        // then we know we won't have a match.
271                        fully_matched = false;
272                        Some(TestBranch::Failure)
273                    }
274                    (Ordering::Greater, true) => {
275                        // test_len < pat_len, and is therefore less
276                        // strict. This can still go both ways.
277                        fully_matched = false;
278                        None
279                    }
280                }
281            }
282
283            (TestKind::Range(test), TestableCase::Range(pat)) => {
284                if test == pat {
285                    fully_matched = true;
286                    Some(TestBranch::Success)
287                } else {
288                    fully_matched = false;
289                    // If the testing range does not overlap with pattern range,
290                    // the pattern can be matched only if this test fails.
291                    if !test.overlaps(pat, self.tcx)? { Some(TestBranch::Failure) } else { None }
292                }
293            }
294            (TestKind::Range(range), &TestableCase::Constant { value }) => {
295                fully_matched = false;
296                if !range.contains(value, self.tcx)? {
297                    // `value` is not contained in the testing range,
298                    // so `value` can be matched only if this test fails.
299                    Some(TestBranch::Failure)
300                } else {
301                    None
302                }
303            }
304
305            (TestKind::Eq { value: test_val, .. }, TestableCase::Constant { value: case_val }) => {
306                if test_val == case_val {
307                    fully_matched = true;
308                    Some(TestBranch::Success)
309                } else {
310                    fully_matched = false;
311                    Some(TestBranch::Failure)
312                }
313            }
314
315            (TestKind::Deref { temp: test_temp, .. }, TestableCase::Deref { temp, .. })
316                if test_temp == temp =>
317            {
318                fully_matched = true;
319                Some(TestBranch::Success)
320            }
321
322            (TestKind::Never, _) => {
323                fully_matched = true;
324                Some(TestBranch::Success)
325            }
326
327            (
328                TestKind::Switch { .. }
329                | TestKind::SwitchInt { .. }
330                | TestKind::If
331                | TestKind::Len { .. }
332                | TestKind::Range { .. }
333                | TestKind::Eq { .. }
334                | TestKind::Deref { .. },
335                _,
336            ) => {
337                fully_matched = false;
338                None
339            }
340        };
341
342        if fully_matched {
343            // Replace the match pair by its sub-pairs.
344            let match_pair = candidate.match_pairs.remove(match_pair_index);
345            candidate.match_pairs.extend(match_pair.subpairs);
346            // Move or-patterns to the end.
347            candidate.sort_match_pairs();
348        }
349
350        ret
351    }
352}