1use std::cmp::Ordering;
23use rustc_data_structures::fx::FxIndexMap;
4use rustc_middle::mir::Place;
5use rustc_middle::span_bug;
6use tracing::debug;
78use crate::builder::Builder;
9use crate::builder::matches::{
10Candidate, PatConstKind, SliceLenOp, Test, TestBranch, TestKind, TestableCase,
11};
1213/// Output of [`Builder::partition_candidates_into_buckets`].
14pub(crate) struct PartitionedCandidates<'tcx, 'b, 'c> {
15/// For each possible outcome of the test, the candidates that are matched in that outcome.
16pub(crate) target_candidates: FxIndexMap<TestBranch<'tcx>, Vec<&'b mut Candidate<'tcx>>>,
17/// The remaining candidates that weren't associated with any test outcome.
18pub(crate) remaining_candidates: &'b mut [&'c mut Candidate<'tcx>],
19}
2021impl<'a, 'tcx> Builder<'a, 'tcx> {
22/// Given a test, we partition the input candidates into several buckets.
23 /// If a candidate matches in exactly one of the branches of `test`
24 /// (and no other branches), we put it into the corresponding bucket.
25 /// If it could match in more than one of the branches of `test`, the test
26 /// doesn't usefully apply to it, and we stop partitioning candidates.
27 ///
28 /// Importantly, we also **mutate** the branched candidates to remove match pairs
29 /// that are entailed by the outcome of the test, and add any sub-pairs of the
30 /// removed pairs.
31 ///
32 /// For example:
33 /// ```
34 /// # let (x, y, z) = (true, true, true);
35 /// match (x, y, z) {
36 /// (true , _ , true ) => true, // (0)
37 /// (false, false, _ ) => false, // (1)
38 /// (_ , true , _ ) => true, // (2)
39 /// (true , _ , false) => false, // (3)
40 /// }
41 /// # ;
42 /// ```
43 ///
44 /// Assume we are testing on `x`. Conceptually, there are 2 overlapping candidate sets:
45 /// - If the outcome is that `x` is true, candidates {0, 2, 3} are possible
46 /// - If the outcome is that `x` is false, candidates {1, 2} are possible
47 ///
48 /// Following our algorithm:
49 /// - Candidate 0 is bucketed into outcome `x == true`
50 /// - Candidate 1 is bucketed into outcome `x == false`
51 /// - Candidate 2 remains unbucketed, because testing `x` has no effect on it
52 /// - Candidate 3 remains unbucketed, because a previous candidate (2) was unbucketed
53 /// - This helps preserve the illusion that candidates are tested "in order"
54 ///
55 /// The bucketed candidates are mutated to remove entailed match pairs:
56 /// - candidate 0 becomes `[z @ true]` since we know that `x` was `true`;
57 /// - candidate 1 becomes `[y @ false]` since we know that `x` was `false`.
58pub(super) fn partition_candidates_into_buckets<'b, 'c>(
59&mut self,
60 match_place: Place<'tcx>,
61 test: &Test<'tcx>,
62mut candidates: &'b mut [&'c mut Candidate<'tcx>],
63 ) -> PartitionedCandidates<'tcx, 'b, 'c> {
64// For each of the possible outcomes, collect a vector of candidates that apply if the test
65 // has that particular outcome.
66let mut target_candidates: FxIndexMap<_, Vec<&mut Candidate<'_>>> = Default::default();
6768let total_candidate_count = candidates.len();
6970// Partition the candidates into the appropriate vector in `target_candidates`.
71 // Note that at some point we may encounter a candidate where the test is not relevant;
72 // at that point, we stop partitioning.
73while let Some(candidate) = candidates.first_mut() {
74let Some(branch) =
75self.choose_bucket_for_candidate(match_place, test, candidate, &target_candidates)
76else {
77break;
78 };
79let (candidate, rest) = candidates.split_first_mut().unwrap();
80 target_candidates.entry(branch).or_insert_with(Vec::new).push(candidate);
81 candidates = rest;
82 }
8384// At least the first candidate ought to be tested
85if !(total_candidate_count > candidates.len()) {
{
::core::panicking::panic_fmt(format_args!("{0}, {1:#?}",
total_candidate_count, candidates));
}
};assert!(
86 total_candidate_count > candidates.len(),
87"{total_candidate_count}, {candidates:#?}"
88);
89{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_build/src/builder/matches/buckets.rs:89",
"rustc_mir_build::builder::matches::buckets",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_build/src/builder/matches/buckets.rs"),
::tracing_core::__macro_support::Option::Some(89u32),
::tracing_core::__macro_support::Option::Some("rustc_mir_build::builder::matches::buckets"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("tested_candidates: {0}",
total_candidate_count - candidates.len()) as &dyn Value))])
});
} else { ; }
};debug!("tested_candidates: {}", total_candidate_count - candidates.len());
90{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_build/src/builder/matches/buckets.rs:90",
"rustc_mir_build::builder::matches::buckets",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_build/src/builder/matches/buckets.rs"),
::tracing_core::__macro_support::Option::Some(90u32),
::tracing_core::__macro_support::Option::Some("rustc_mir_build::builder::matches::buckets"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("untested_candidates: {0}",
candidates.len()) as &dyn Value))])
});
} else { ; }
};debug!("untested_candidates: {}", candidates.len());
9192PartitionedCandidates { target_candidates, remaining_candidates: candidates }
93 }
9495/// Given that we are performing `test` against `test_place`, this job
96 /// sorts out what the status of `candidate` will be after the test. See
97 /// `test_candidates` for the usage of this function. The candidate may
98 /// be modified to update its `match_pairs`.
99 ///
100 /// So, for example, if this candidate is `x @ Some(P0)` and the `Test` is
101 /// a variant test, then we would modify the candidate to be `(x as
102 /// Option).0 @ P0` and return the index corresponding to the variant
103 /// `Some`.
104 ///
105 /// However, in some cases, the test may just not be relevant to candidate.
106 /// For example, suppose we are testing whether `foo.x == 22`, but in one
107 /// match arm we have `Foo { x: _, ... }`... in that case, the test for
108 /// the value of `x` has no particular relevance to this candidate. In
109 /// such cases, this function just returns None without doing anything.
110 /// This is used by the overall `match_candidates` algorithm to structure
111 /// the match as a whole. See `match_candidates` for more details.
112 ///
113 /// FIXME(#29623). In some cases, we have some tricky choices to make. for
114 /// example, if we are testing that `x == 22`, but the candidate is `x @
115 /// 13..55`, what should we do? In the event that the test is true, we know
116 /// that the candidate applies, but in the event of false, we don't know
117 /// that it *doesn't* apply. For now, we return false, indicate that the
118 /// test does not apply to this candidate, but it might be we can get
119 /// tighter match code if we do something a bit different.
120fn choose_bucket_for_candidate(
121&mut self,
122 test_place: Place<'tcx>,
123 test: &Test<'tcx>,
124 candidate: &mut Candidate<'tcx>,
125// Other candidates that have already been partitioned into a bucket for this test, if any
126prior_candidates: &FxIndexMap<TestBranch<'tcx>, Vec<&mut Candidate<'tcx>>>,
127 ) -> Option<TestBranch<'tcx>> {
128// Find the match_pair for this place (if any). At present,
129 // afaik, there can be at most one. (In the future, if we
130 // adopted a more general `@` operator, there might be more
131 // than one, but it'd be very unusual to have two sides that
132 // both require tests; you'd expect one side to be simplified
133 // away.)
134let (match_pair_index, match_pair) = candidate135 .match_pairs
136 .iter()
137 .enumerate()
138 .find(|&(_, mp)| mp.place == Some(test_place))?;
139140// If true, the match pair is completely entailed by its corresponding test
141 // branch, so it can be removed. If false, the match pair is _compatible_
142 // with its test branch, but still needs a more specific test.
143let fully_matched;
144let ret = match (&test.kind, &match_pair.testable_case) {
145// If we are performing a variant switch, then this
146 // informs variant patterns, but nothing else.
147(
148&TestKind::Switch { adt_def: tested_adt_def },
149&TestableCase::Variant { adt_def, variant_index },
150 ) => {
151match (&adt_def, &tested_adt_def) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
let kind = ::core::panicking::AssertKind::Eq;
::core::panicking::assert_failed(kind, &*left_val, &*right_val,
::core::option::Option::None);
}
}
};assert_eq!(adt_def, tested_adt_def);
152fully_matched = true;
153Some(TestBranch::Variant(variant_index))
154 }
155156// If we are performing a switch over integers, then this informs integer
157 // equality, but nothing else.
158 //
159 // FIXME(#29623) we could use PatKind::Range to rule
160 // things out here, in some cases.
161(
162 TestKind::SwitchInt,
163&TestableCase::Constant { value, kind: PatConstKind::IntOrChar },
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.
170let is_covering_range = |testable_case: &TestableCase<'tcx>| {
171testable_case.as_range().is_some_and(|range| {
172#[allow(non_exhaustive_omitted_patterns)] match range.contains(value,
self.tcx) {
None | Some(true) => true,
_ => false,
}matches!(range.contains(value, self.tcx), None | Some(true))173 })
174 };
175let is_conflicting_candidate = |candidate: &&mut Candidate<'tcx>| {
176candidate.match_pairs.iter().any(|mp| {
177mp.place == Some(test_place) && is_covering_range(&mp.testable_case)
178 })
179 };
180if prior_candidates181 .get(&TestBranch::Failure)
182 .is_some_and(|candidates| candidates.iter().any(is_conflicting_candidate))
183 {
184fully_matched = false;
185None186 } else {
187fully_matched = true;
188Some(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.)
196fully_matched = false;
197let not_contained = prior_candidates198 .keys()
199 .filter_map(|br| br.as_constant())
200 .all(|val| #[allow(non_exhaustive_omitted_patterns)] match range.contains(val, self.tcx)
{
Some(false) => true,
_ => false,
}matches!(range.contains(val, self.tcx), Some(false)));
201202not_contained.then(|| {
203// No switch values are contained in the pattern range,
204 // so the pattern can be matched only if this test fails.
205TestBranch::Failure206 })
207 }
208209 (TestKind::If, TestableCase::Constant { value, kind: PatConstKind::Bool }) => {
210fully_matched = true;
211let value = value.try_to_bool().unwrap_or_else(|| {
212::rustc_middle::util::bug::span_bug_fmt(test.span,
format_args!("expected boolean value but got {0:?}", value))span_bug!(test.span, "expected boolean value but got {value:?}")213 });
214Some(if value { TestBranch::Success } else { TestBranch::Failure })
215 }
216217// Determine how the proposed slice-length test interacts with the
218 // slice pattern we're currently looking at.
219 //
220 // Keep in mind the invariant that a case is not allowed to succeed
221 // on multiple arms of the same test. For example, even though the
222 // test `len == 4` logically implies `len >= 4` on its success arm,
223 // the case `len >= 4` could also succeed on the test's failure arm,
224 // so it can't be included in the success bucket or failure bucket.
225(
226&TestKind::SliceLen { len: test_len, op: SliceLenOp::Equal },
227&TestableCase::Slice { len: pat_len, op: pat_op },
228 ) => {
229match (test_len.cmp(&pat_len), pat_op) {
230 (Ordering::Equal, SliceLenOp::Equal) => {
231// E.g. test is `len == 4` and pattern is `len == 4`.
232 // Pattern is fully matched on the success arm.
233fully_matched = true;
234Some(TestBranch::Success)
235 }
236 (Ordering::Less, _) => {
237// E.g. test is `len == 4` and pattern is `len == 5` or `len >= 5`.
238 // Pattern can only succeed on the failure arm, but isn't fully matched.
239fully_matched = false;
240Some(TestBranch::Failure)
241 }
242 (Ordering::Equal | Ordering::Greater, SliceLenOp::GreaterOrEqual) => {
243// E.g. test is `len == 4` and pattern is `len >= 4` or `len >= 3`.
244 // Pattern could succeed on both arms, so it can't be bucketed.
245fully_matched = false;
246None247 }
248 (Ordering::Greater, SliceLenOp::Equal) => {
249// E.g. test is `len == 4` and pattern is `len == 3`.
250 // Pattern can only succeed on the failure arm, but isn't fully matched.
251fully_matched = false;
252Some(TestBranch::Failure)
253 }
254 }
255 }
256 (
257&TestKind::SliceLen { len: test_len, op: SliceLenOp::GreaterOrEqual },
258&TestableCase::Slice { len: pat_len, op: pat_op },
259 ) => {
260match (test_len.cmp(&pat_len), pat_op) {
261 (Ordering::Equal, SliceLenOp::GreaterOrEqual) => {
262// E.g. test is `len >= 4` and pattern is `len >= 4`.
263 // Pattern is fully matched on the success arm.
264fully_matched = true;
265Some(TestBranch::Success)
266 }
267 (Ordering::Less, _) | (Ordering::Equal, SliceLenOp::Equal) => {
268// E.g. test is `len >= 4` and pattern is `len == 5` or `len >= 5` or `len == 4`.
269 // Pattern can only succeed on the success arm, but isn't fully matched.
270fully_matched = false;
271Some(TestBranch::Success)
272 }
273 (Ordering::Greater, SliceLenOp::Equal) => {
274// E.g. test is `len >= 4` and pattern is `len == 3`.
275 // Pattern can only succeed on the failure arm, but isn't fully matched.
276fully_matched = false;
277Some(TestBranch::Failure)
278 }
279 (Ordering::Greater, SliceLenOp::GreaterOrEqual) => {
280// E.g. test is `len >= 4` and pattern is `len >= 3`.
281 // Pattern could succeed on both arms, so it can't be bucketed.
282fully_matched = false;
283None284 }
285 }
286 }
287288 (TestKind::Range(test), TestableCase::Range(pat)) => {
289if test == pat {
290fully_matched = true;
291Some(TestBranch::Success)
292 } else {
293fully_matched = false;
294// If the testing range does not overlap with pattern range,
295 // the pattern can be matched only if this test fails.
296if !test.overlaps(pat, self.tcx)? { Some(TestBranch::Failure) } else { None }
297 }
298 }
299 (
300 TestKind::Range(range),
301&TestableCase::Constant {
302 value,
303 kind: PatConstKind::Bool | PatConstKind::IntOrChar | PatConstKind::Float,
304 },
305 ) => {
306fully_matched = false;
307if !range.contains(value, self.tcx)? {
308// `value` is not contained in the testing range,
309 // so `value` can be matched only if this test fails.
310Some(TestBranch::Failure)
311 } else {
312None313 }
314 }
315316 (
317 TestKind::StringEq { value: test_val, .. },
318 TestableCase::Constant { value: case_val, kind: PatConstKind::String },
319 )
320 | (
321 TestKind::ScalarEq { value: test_val, .. },
322 TestableCase::Constant {
323 value: case_val,
324 kind: PatConstKind::Float | PatConstKind::Other,
325 },
326 ) => {
327if test_val == case_val {
328fully_matched = true;
329Some(TestBranch::Success)
330 } else {
331fully_matched = false;
332Some(TestBranch::Failure)
333 }
334 }
335336 (TestKind::Deref { temp: test_temp, .. }, TestableCase::Deref { temp, .. })
337if test_temp == temp =>
338 {
339fully_matched = true;
340Some(TestBranch::Success)
341 }
342343 (TestKind::Never, _) => {
344fully_matched = true;
345Some(TestBranch::Success)
346 }
347348 (
349 TestKind::Switch { .. }
350 | TestKind::SwitchInt { .. }
351 | TestKind::If352 | TestKind::SliceLen { .. }
353 | TestKind::Range { .. }
354 | TestKind::StringEq { .. }
355 | TestKind::ScalarEq { .. }
356 | TestKind::Deref { .. },
357_,
358 ) => {
359fully_matched = false;
360None361 }
362 };
363364if fully_matched {
365// Replace the match pair by its sub-pairs.
366let match_pair = candidate.match_pairs.remove(match_pair_index);
367candidate.match_pairs.extend(match_pair.subpairs);
368// Move or-patterns to the end.
369candidate.sort_match_pairs();
370 }
371372ret373 }
374}