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}