Skip to main content

rustc_mir_build/builder/matches/
buckets.rs

1use std::cmp::Ordering;
2
3use rustc_data_structures::fx::FxIndexMap;
4use rustc_middle::mir::Place;
5use rustc_span::{bug, span_bug};
6use tracing::debug;
7
8use crate::builder::Builder;
9use crate::builder::matches::{
10    Candidate, MatchPairKind, PatConstKind, SliceLenOp, Test, TestBranch, TestKind, TestableCase,
11};
12
13/// 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.
16    pub(crate) target_candidates: FxIndexMap<TestBranch<'tcx>, Vec<&'b mut Candidate<'tcx>>>,
17    /// The remaining candidates that weren't associated with any test outcome.
18    pub(crate) remaining_candidates: &'b mut [&'c mut Candidate<'tcx>],
19}
20
21impl<'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`.
58    pub(super) fn partition_candidates_into_buckets<'b, 'c>(
59        &mut self,
60        match_place: Place<'tcx>,
61        test: &Test<'tcx>,
62        mut 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.
66        let mut target_candidates: FxIndexMap<_, Vec<&mut Candidate<'_>>> = Default::default();
67
68        let total_candidate_count = candidates.len();
69
70        // 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.
73        while let Some(candidate) = candidates.first_mut() {
74            let Some(branch) =
75                self.choose_bucket_for_candidate(match_place, test, candidate, &target_candidates)
76            else {
77                break;
78            };
79            let (candidate, rest) = candidates.split_first_mut().unwrap();
80            target_candidates.entry(branch).or_insert_with(Vec::new).push(candidate);
81            candidates = rest;
82        }
83
84        // At least the first candidate ought to be tested
85        if !(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 /rustc-dev/923c95cdf5ba65cea505aa2ea829f578e1506ed8/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("/rustc-dev/923c95cdf5ba65cea505aa2ea829f578e1506ed8/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};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("tested_candidates: {0}",
                                                    total_candidate_count - candidates.len()) as
                                            &dyn ::tracing::field::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 /rustc-dev/923c95cdf5ba65cea505aa2ea829f578e1506ed8/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("/rustc-dev/923c95cdf5ba65cea505aa2ea829f578e1506ed8/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};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("untested_candidates: {0}",
                                                    candidates.len()) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("untested_candidates: {}", candidates.len());
91
92        PartitionedCandidates { target_candidates, remaining_candidates: candidates }
93    }
94
95    /// 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.
120    fn 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
126        prior_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.)
134        let (match_pair_index, match_pair_testable_case) =
135            candidate.match_pairs.iter().enumerate().find_map(|(i, mp)| {
136                if let MatchPairKind::Testable { place, ref testable_case, .. } = mp.kind
137                    && place == test_place
138                {
139                    Some((i, testable_case))
140                } else {
141                    None
142                }
143            })?;
144
145        // If true, the match pair is completely entailed by its corresponding test
146        // branch, so it can be removed. If false, the match pair is _compatible_
147        // with its test branch, but still needs a more specific test.
148        let fully_matched;
149        let ret = match (&test.kind, match_pair_testable_case) {
150            // If we are performing a variant switch, then this
151            // informs variant patterns, but nothing else.
152            (
153                &TestKind::Switch { adt_def: tested_adt_def },
154                &TestableCase::Variant { adt_def, variant_index },
155            ) => {
156                {
    match (&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);
157                fully_matched = true;
158                Some(TestBranch::Variant(variant_index))
159            }
160
161            // If we are performing a switch over integers, then this informs integer
162            // equality, but nothing else.
163            //
164            // FIXME(#29623) we could use PatKind::Range to rule
165            // things out here, in some cases.
166            (
167                TestKind::SwitchInt,
168                &TestableCase::Constant { value, kind: PatConstKind::IntOrChar },
169            ) => {
170                // An important invariant of candidate bucketing is that a candidate
171                // must not match in multiple branches. For `SwitchInt` tests, adding
172                // a new value might invalidate that property for range patterns that
173                // have already been partitioned into the failure arm, so we must take care
174                // not to add such values here.
175                let is_covering_range = |testable_case: &TestableCase<'tcx>| {
176                    testable_case.as_range().is_some_and(|range| {
177                        #[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))
178                    })
179                };
180                let is_conflicting_candidate = |candidate: &&mut Candidate<'tcx>| {
181                    candidate.match_pairs.iter().any(|mp| {
182                        #[allow(non_exhaustive_omitted_patterns)] match mp.kind {
    MatchPairKind::Testable { place, ref testable_case, .. } if
        place == test_place && is_covering_range(testable_case) => true,
    _ => false,
}matches!(mp.kind, MatchPairKind::Testable { place, ref testable_case, .. }
183                            if place == test_place && is_covering_range(testable_case)
184                        )
185                    })
186                };
187                if prior_candidates
188                    .get(&TestBranch::Failure)
189                    .is_some_and(|candidates| candidates.iter().any(is_conflicting_candidate))
190                {
191                    fully_matched = false;
192                    None
193                } else {
194                    fully_matched = true;
195                    Some(TestBranch::Constant(value))
196                }
197            }
198            (TestKind::SwitchInt, TestableCase::Range(range)) => {
199                // When performing a `SwitchInt` test, a range pattern can be
200                // sorted into the failure arm if it doesn't contain _any_ of
201                // the values being tested. (This restricts what values can be
202                // added to the test by subsequent candidates.)
203                fully_matched = false;
204                let not_contained = prior_candidates
205                    .keys()
206                    .filter_map(|br| br.as_constant())
207                    .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)));
208
209                not_contained.then(|| {
210                    // No switch values are contained in the pattern range,
211                    // so the pattern can be matched only if this test fails.
212                    TestBranch::Failure
213                })
214            }
215
216            (TestKind::If, TestableCase::Constant { value, kind: PatConstKind::Bool }) => {
217                fully_matched = true;
218                let value = value.try_to_bool().unwrap_or_else(|| {
219                    bug_impl(Some(test.span),
    format_args!("expected boolean value but got {0:?}", value),
    Location::caller())span_bug!(test.span, "expected boolean value but got {value:?}")
220                });
221                Some(if value { TestBranch::Success } else { TestBranch::Failure })
222            }
223
224            // Determine how the proposed slice-length test interacts with the
225            // slice pattern we're currently looking at.
226            //
227            // Keep in mind the invariant that a case is not allowed to succeed
228            // on multiple arms of the same test. For example, even though the
229            // test `len == 4` logically implies `len >= 4` on its success arm,
230            // the case `len >= 4` could also succeed on the test's failure arm,
231            // so it can't be included in the success bucket or failure bucket.
232            (
233                &TestKind::SliceLen { len: test_len, op: SliceLenOp::Equal },
234                &TestableCase::Slice { len: pat_len, op: pat_op },
235            ) => {
236                match (test_len.cmp(&pat_len), pat_op) {
237                    (Ordering::Equal, SliceLenOp::Equal) => {
238                        // E.g. test is `len == 4` and pattern is `len == 4`.
239                        // Pattern is fully matched on the success arm.
240                        fully_matched = true;
241                        Some(TestBranch::Success)
242                    }
243                    (Ordering::Less, _) => {
244                        // E.g. test is `len == 4` and pattern is `len == 5` or `len >= 5`.
245                        // Pattern can only succeed on the failure arm, but isn't fully matched.
246                        fully_matched = false;
247                        Some(TestBranch::Failure)
248                    }
249                    (Ordering::Equal | Ordering::Greater, SliceLenOp::GreaterOrEqual) => {
250                        // E.g. test is `len == 4` and pattern is `len >= 4` or `len >= 3`.
251                        // Pattern could succeed on both arms, so it can't be bucketed.
252                        fully_matched = false;
253                        None
254                    }
255                    (Ordering::Greater, SliceLenOp::Equal) => {
256                        // E.g. test is `len == 4` and pattern is `len == 3`.
257                        // Pattern can only succeed on the failure arm, but isn't fully matched.
258                        fully_matched = false;
259                        Some(TestBranch::Failure)
260                    }
261                }
262            }
263            (
264                &TestKind::SliceLen { len: test_len, op: SliceLenOp::GreaterOrEqual },
265                &TestableCase::Slice { len: pat_len, op: pat_op },
266            ) => {
267                match (test_len.cmp(&pat_len), pat_op) {
268                    (Ordering::Equal, SliceLenOp::GreaterOrEqual) => {
269                        // E.g. test is `len >= 4` and pattern is `len >= 4`.
270                        // Pattern is fully matched on the success arm.
271                        fully_matched = true;
272                        Some(TestBranch::Success)
273                    }
274                    (Ordering::Less, _) | (Ordering::Equal, SliceLenOp::Equal) => {
275                        // E.g. test is `len >= 4` and pattern is `len == 5` or `len >= 5` or `len == 4`.
276                        // Pattern can only succeed on the success arm, but isn't fully matched.
277                        fully_matched = false;
278                        Some(TestBranch::Success)
279                    }
280                    (Ordering::Greater, SliceLenOp::Equal) => {
281                        // E.g. test is `len >= 4` and pattern is `len == 3`.
282                        // Pattern can only succeed on the failure arm, but isn't fully matched.
283                        fully_matched = false;
284                        Some(TestBranch::Failure)
285                    }
286                    (Ordering::Greater, SliceLenOp::GreaterOrEqual) => {
287                        // E.g. test is `len >= 4` and pattern is `len >= 3`.
288                        // Pattern could succeed on both arms, so it can't be bucketed.
289                        fully_matched = false;
290                        None
291                    }
292                }
293            }
294
295            (TestKind::Range(test), TestableCase::Range(pat)) => {
296                if test == pat {
297                    fully_matched = true;
298                    Some(TestBranch::Success)
299                } else {
300                    fully_matched = false;
301                    // If the testing range does not overlap with pattern range,
302                    // the pattern can be matched only if this test fails.
303                    if !test.overlaps(pat, self.tcx)? { Some(TestBranch::Failure) } else { None }
304                }
305            }
306            (
307                TestKind::Range(range),
308                &TestableCase::Constant {
309                    value,
310                    kind: PatConstKind::Bool | PatConstKind::IntOrChar | PatConstKind::Float,
311                },
312            ) => {
313                fully_matched = false;
314                if !range.contains(value, self.tcx)? {
315                    // `value` is not contained in the testing range,
316                    // so `value` can be matched only if this test fails.
317                    Some(TestBranch::Failure)
318                } else {
319                    None
320                }
321            }
322
323            (
324                TestKind::StringEq { value: test_val, .. },
325                TestableCase::Constant { value: case_val, kind: PatConstKind::String },
326            )
327            | (
328                TestKind::ScalarEq { value: test_val, .. },
329                TestableCase::Constant {
330                    value: case_val,
331                    kind: PatConstKind::Float | PatConstKind::Other,
332                },
333            ) => {
334                if test_val == case_val {
335                    fully_matched = true;
336                    Some(TestBranch::Success)
337                } else {
338                    fully_matched = false;
339                    Some(TestBranch::Failure)
340                }
341            }
342
343            (TestKind::Deref { temp: test_temp, .. }, TestableCase::Deref { temp, .. })
344                if test_temp == temp =>
345            {
346                fully_matched = true;
347                Some(TestBranch::Success)
348            }
349
350            (TestKind::Never, _) => {
351                fully_matched = true;
352                Some(TestBranch::Success)
353            }
354
355            (
356                TestKind::Switch { .. }
357                | TestKind::SwitchInt { .. }
358                | TestKind::If
359                | TestKind::SliceLen { .. }
360                | TestKind::Range { .. }
361                | TestKind::StringEq { .. }
362                | TestKind::ScalarEq { .. }
363                | TestKind::Deref { .. },
364                _,
365            ) => {
366                fully_matched = false;
367                None
368            }
369        };
370
371        if fully_matched {
372            // Replace the match pair by its sub-pairs.
373            let match_pair = candidate.match_pairs.remove(match_pair_index);
374            let MatchPairKind::Testable { subpairs, .. } = match_pair.kind else {
375                bug_impl(None, format_args!("match pair must have been refutable"),
    Location::caller());bug!("match pair must have been refutable");
376            };
377            candidate.match_pairs.extend(subpairs);
378            // Move or-patterns to the end.
379            candidate.sort_match_pairs();
380        }
381
382        ret
383    }
384}