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