No need to collect test variants ahead of time
This commit is contained in:
parent
8c3688cbb5
commit
edea739292
2 changed files with 38 additions and 147 deletions
|
@ -14,7 +14,6 @@ use rustc_data_structures::{
|
||||||
fx::{FxHashSet, FxIndexMap, FxIndexSet},
|
fx::{FxHashSet, FxIndexMap, FxIndexSet},
|
||||||
stack::ensure_sufficient_stack,
|
stack::ensure_sufficient_stack,
|
||||||
};
|
};
|
||||||
use rustc_index::bit_set::BitSet;
|
|
||||||
use rustc_middle::middle::region;
|
use rustc_middle::middle::region;
|
||||||
use rustc_middle::mir::{self, *};
|
use rustc_middle::mir::{self, *};
|
||||||
use rustc_middle::thir::{self, *};
|
use rustc_middle::thir::{self, *};
|
||||||
|
@ -1116,19 +1115,10 @@ enum TestKind<'tcx> {
|
||||||
Switch {
|
Switch {
|
||||||
/// The enum type being tested.
|
/// The enum type being tested.
|
||||||
adt_def: ty::AdtDef<'tcx>,
|
adt_def: ty::AdtDef<'tcx>,
|
||||||
/// The set of variants that we should create a branch for. We also
|
|
||||||
/// create an additional "otherwise" case.
|
|
||||||
variants: BitSet<VariantIdx>,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Test what value an integer or `char` has.
|
/// Test what value an integer or `char` has.
|
||||||
SwitchInt {
|
SwitchInt,
|
||||||
/// The (ordered) set of values that we test for.
|
|
||||||
///
|
|
||||||
/// We create a branch to each of the values in `options`, as well as an "otherwise" branch
|
|
||||||
/// for all other values, even in the (rare) case that `options` is exhaustive.
|
|
||||||
options: FxIndexMap<Const<'tcx>, u128>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Test what value a `bool` has.
|
/// Test what value a `bool` has.
|
||||||
If,
|
If,
|
||||||
|
@ -1173,6 +1163,12 @@ enum TestBranch<'tcx> {
|
||||||
Failure,
|
Failure,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'tcx> TestBranch<'tcx> {
|
||||||
|
fn as_constant(&self) -> Option<&Const<'tcx>> {
|
||||||
|
if let Self::Constant(v, _) = self { Some(v) } else { None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `ArmHasGuard` is a wrapper around a boolean flag. It indicates whether
|
/// `ArmHasGuard` is a wrapper around a boolean flag. It indicates whether
|
||||||
/// a match arm has a guard expression attached to it.
|
/// a match arm has a guard expression attached to it.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
@ -1583,30 +1579,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
) -> (PlaceBuilder<'tcx>, Test<'tcx>) {
|
) -> (PlaceBuilder<'tcx>, Test<'tcx>) {
|
||||||
// Extract the match-pair from the highest priority candidate
|
// Extract the match-pair from the highest priority candidate
|
||||||
let match_pair = &candidates.first().unwrap().match_pairs[0];
|
let match_pair = &candidates.first().unwrap().match_pairs[0];
|
||||||
let mut test = self.test(match_pair);
|
let test = self.test(match_pair);
|
||||||
let match_place = match_pair.place.clone();
|
let match_place = match_pair.place.clone();
|
||||||
|
|
||||||
debug!(?test, ?match_pair);
|
debug!(?test, ?match_pair);
|
||||||
// Most of the time, the test to perform is simply a function of the main candidate; but for
|
|
||||||
// a test like SwitchInt, we may want to add cases based on the candidates that are
|
|
||||||
// available
|
|
||||||
match test.kind {
|
|
||||||
TestKind::SwitchInt { ref mut options } => {
|
|
||||||
for candidate in candidates.iter() {
|
|
||||||
if !self.add_cases_to_switch(&match_place, candidate, options) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TestKind::Switch { adt_def: _, ref mut variants } => {
|
|
||||||
for candidate in candidates.iter() {
|
|
||||||
if !self.add_variants_to_switch(&match_place, candidate, variants) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
(match_place, test)
|
(match_place, test)
|
||||||
}
|
}
|
||||||
|
@ -1663,7 +1638,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
// point we may encounter a candidate where the test is not relevant; at that point, we stop
|
// point we may encounter a candidate where the test is not relevant; at that point, we stop
|
||||||
// sorting.
|
// sorting.
|
||||||
while let Some(candidate) = candidates.first_mut() {
|
while let Some(candidate) = candidates.first_mut() {
|
||||||
let Some(branch) = self.sort_candidate(&match_place, &test, candidate) else {
|
let Some(branch) =
|
||||||
|
self.sort_candidate(&match_place, test, candidate, &target_candidates)
|
||||||
|
else {
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
let (candidate, rest) = candidates.split_first_mut().unwrap();
|
let (candidate, rest) = candidates.split_first_mut().unwrap();
|
||||||
|
|
|
@ -10,9 +10,7 @@ use crate::build::matches::{Candidate, MatchPair, Test, TestBranch, TestCase, Te
|
||||||
use crate::build::Builder;
|
use crate::build::Builder;
|
||||||
use rustc_data_structures::fx::FxIndexMap;
|
use rustc_data_structures::fx::FxIndexMap;
|
||||||
use rustc_hir::{LangItem, RangeEnd};
|
use rustc_hir::{LangItem, RangeEnd};
|
||||||
use rustc_index::bit_set::BitSet;
|
|
||||||
use rustc_middle::mir::*;
|
use rustc_middle::mir::*;
|
||||||
use rustc_middle::thir::*;
|
|
||||||
use rustc_middle::ty::util::IntTypeExt;
|
use rustc_middle::ty::util::IntTypeExt;
|
||||||
use rustc_middle::ty::GenericArg;
|
use rustc_middle::ty::GenericArg;
|
||||||
use rustc_middle::ty::{self, adjustment::PointerCoercion, Ty, TyCtxt};
|
use rustc_middle::ty::{self, adjustment::PointerCoercion, Ty, TyCtxt};
|
||||||
|
@ -20,7 +18,6 @@ use rustc_span::def_id::DefId;
|
||||||
use rustc_span::source_map::Spanned;
|
use rustc_span::source_map::Spanned;
|
||||||
use rustc_span::symbol::{sym, Symbol};
|
use rustc_span::symbol::{sym, Symbol};
|
||||||
use rustc_span::{Span, DUMMY_SP};
|
use rustc_span::{Span, DUMMY_SP};
|
||||||
use rustc_target::abi::VariantIdx;
|
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
@ -30,22 +27,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
/// It is a bug to call this with a not-fully-simplified pattern.
|
/// It is a bug to call this with a not-fully-simplified pattern.
|
||||||
pub(super) fn test<'pat>(&mut self, match_pair: &MatchPair<'pat, 'tcx>) -> Test<'tcx> {
|
pub(super) fn test<'pat>(&mut self, match_pair: &MatchPair<'pat, 'tcx>) -> Test<'tcx> {
|
||||||
let kind = match match_pair.test_case {
|
let kind = match match_pair.test_case {
|
||||||
TestCase::Variant { adt_def, variant_index: _ } => {
|
TestCase::Variant { adt_def, variant_index: _ } => TestKind::Switch { adt_def },
|
||||||
TestKind::Switch { adt_def, variants: BitSet::new_empty(adt_def.variants().len()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
TestCase::Constant { .. } if match_pair.pattern.ty.is_bool() => TestKind::If,
|
TestCase::Constant { .. } if match_pair.pattern.ty.is_bool() => TestKind::If,
|
||||||
|
TestCase::Constant { .. } if is_switch_ty(match_pair.pattern.ty) => TestKind::SwitchInt,
|
||||||
TestCase::Constant { .. } if is_switch_ty(match_pair.pattern.ty) => {
|
|
||||||
// For integers, we use a `SwitchInt` match, which allows
|
|
||||||
// us to handle more cases.
|
|
||||||
TestKind::SwitchInt {
|
|
||||||
// these maps are empty to start; cases are
|
|
||||||
// added below in add_cases_to_switch
|
|
||||||
options: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TestCase::Constant { value } => TestKind::Eq { value, ty: match_pair.pattern.ty },
|
TestCase::Constant { value } => TestKind::Eq { value, ty: match_pair.pattern.ty },
|
||||||
|
|
||||||
TestCase::Range(range) => {
|
TestCase::Range(range) => {
|
||||||
|
@ -70,57 +55,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
Test { span: match_pair.pattern.span, kind }
|
Test { span: match_pair.pattern.span, kind }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn add_cases_to_switch<'pat>(
|
|
||||||
&mut self,
|
|
||||||
test_place: &PlaceBuilder<'tcx>,
|
|
||||||
candidate: &Candidate<'pat, 'tcx>,
|
|
||||||
options: &mut FxIndexMap<Const<'tcx>, u128>,
|
|
||||||
) -> bool {
|
|
||||||
let Some(match_pair) = candidate.match_pairs.iter().find(|mp| mp.place == *test_place)
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
match match_pair.test_case {
|
|
||||||
TestCase::Constant { value } => {
|
|
||||||
options.entry(value).or_insert_with(|| value.eval_bits(self.tcx, self.param_env));
|
|
||||||
true
|
|
||||||
}
|
|
||||||
TestCase::Variant { .. } => {
|
|
||||||
panic!("you should have called add_variants_to_switch instead!");
|
|
||||||
}
|
|
||||||
TestCase::Range(ref range) => {
|
|
||||||
// Check that none of the switch values are in the range.
|
|
||||||
self.values_not_contained_in_range(&*range, options).unwrap_or(false)
|
|
||||||
}
|
|
||||||
// don't know how to add these patterns to a switch
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn add_variants_to_switch<'pat>(
|
|
||||||
&mut self,
|
|
||||||
test_place: &PlaceBuilder<'tcx>,
|
|
||||||
candidate: &Candidate<'pat, 'tcx>,
|
|
||||||
variants: &mut BitSet<VariantIdx>,
|
|
||||||
) -> bool {
|
|
||||||
let Some(match_pair) = candidate.match_pairs.iter().find(|mp| mp.place == *test_place)
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
match match_pair.test_case {
|
|
||||||
TestCase::Variant { variant_index, .. } => {
|
|
||||||
// We have a pattern testing for variant `variant_index`
|
|
||||||
// set the corresponding index to true
|
|
||||||
variants.insert(variant_index);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
// don't know how to add these patterns to a switch
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(self, target_blocks, place_builder), level = "debug")]
|
#[instrument(skip(self, target_blocks, place_builder), level = "debug")]
|
||||||
pub(super) fn perform_test(
|
pub(super) fn perform_test(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -139,33 +73,20 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
|
|
||||||
let source_info = self.source_info(test.span);
|
let source_info = self.source_info(test.span);
|
||||||
match test.kind {
|
match test.kind {
|
||||||
TestKind::Switch { adt_def, ref variants } => {
|
TestKind::Switch { adt_def } => {
|
||||||
// Variants is a BitVec of indexes into adt_def.variants.
|
|
||||||
let num_enum_variants = adt_def.variants().len();
|
|
||||||
let otherwise_block = target_block(TestBranch::Failure);
|
let otherwise_block = target_block(TestBranch::Failure);
|
||||||
let tcx = self.tcx;
|
|
||||||
let switch_targets = SwitchTargets::new(
|
let switch_targets = SwitchTargets::new(
|
||||||
adt_def.discriminants(tcx).filter_map(|(idx, discr)| {
|
adt_def.discriminants(self.tcx).filter_map(|(idx, discr)| {
|
||||||
if variants.contains(idx) {
|
if let Some(&block) = target_blocks.get(&TestBranch::Variant(idx)) {
|
||||||
debug_assert_ne!(
|
Some((discr.val, block))
|
||||||
target_block(TestBranch::Variant(idx)),
|
|
||||||
otherwise_block,
|
|
||||||
"no candidates for tested discriminant: {discr:?}",
|
|
||||||
);
|
|
||||||
Some((discr.val, target_block(TestBranch::Variant(idx))))
|
|
||||||
} else {
|
} else {
|
||||||
debug_assert_eq!(
|
|
||||||
target_block(TestBranch::Variant(idx)),
|
|
||||||
otherwise_block,
|
|
||||||
"found candidates for untested discriminant: {discr:?}",
|
|
||||||
);
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
otherwise_block,
|
otherwise_block,
|
||||||
);
|
);
|
||||||
debug!("num_enum_variants: {}, variants: {:?}", num_enum_variants, variants);
|
debug!("num_enum_variants: {}", adt_def.variants().len());
|
||||||
let discr_ty = adt_def.repr().discr_type().to_ty(tcx);
|
let discr_ty = adt_def.repr().discr_type().to_ty(self.tcx);
|
||||||
let discr = self.temp(discr_ty, test.span);
|
let discr = self.temp(discr_ty, test.span);
|
||||||
self.cfg.push_assign(
|
self.cfg.push_assign(
|
||||||
block,
|
block,
|
||||||
|
@ -183,13 +104,17 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TestKind::SwitchInt { ref options } => {
|
TestKind::SwitchInt => {
|
||||||
// The switch may be inexhaustive so we have a catch-all block
|
// The switch may be inexhaustive so we have a catch-all block
|
||||||
let otherwise_block = target_block(TestBranch::Failure);
|
let otherwise_block = target_block(TestBranch::Failure);
|
||||||
let switch_targets = SwitchTargets::new(
|
let switch_targets = SwitchTargets::new(
|
||||||
options
|
target_blocks.iter().filter_map(|(&branch, &block)| {
|
||||||
.iter()
|
if let TestBranch::Constant(_, bits) = branch {
|
||||||
.map(|(&val, &bits)| (bits, target_block(TestBranch::Constant(val, bits)))),
|
Some((bits, block))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}),
|
||||||
otherwise_block,
|
otherwise_block,
|
||||||
);
|
);
|
||||||
let terminator = TerminatorKind::SwitchInt {
|
let terminator = TerminatorKind::SwitchInt {
|
||||||
|
@ -548,11 +473,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
/// that it *doesn't* apply. For now, we return false, indicate that the
|
/// that it *doesn't* apply. For now, we return false, indicate that the
|
||||||
/// test does not apply to this candidate, but it might be we can get
|
/// test does not apply to this candidate, but it might be we can get
|
||||||
/// tighter match code if we do something a bit different.
|
/// tighter match code if we do something a bit different.
|
||||||
pub(super) fn sort_candidate<'pat>(
|
pub(super) fn sort_candidate(
|
||||||
&mut self,
|
&mut self,
|
||||||
test_place: &PlaceBuilder<'tcx>,
|
test_place: &PlaceBuilder<'tcx>,
|
||||||
test: &Test<'tcx>,
|
test: &Test<'tcx>,
|
||||||
candidate: &mut Candidate<'pat, 'tcx>,
|
candidate: &mut Candidate<'_, 'tcx>,
|
||||||
|
sorted_candidates: &FxIndexMap<TestBranch<'tcx>, Vec<&mut Candidate<'_, 'tcx>>>,
|
||||||
) -> Option<TestBranch<'tcx>> {
|
) -> Option<TestBranch<'tcx>> {
|
||||||
// Find the match_pair for this place (if any). At present,
|
// Find the match_pair for this place (if any). At present,
|
||||||
// afaik, there can be at most one. (In the future, if we
|
// afaik, there can be at most one. (In the future, if we
|
||||||
|
@ -568,7 +494,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
// If we are performing a variant switch, then this
|
// If we are performing a variant switch, then this
|
||||||
// informs variant patterns, but nothing else.
|
// informs variant patterns, but nothing else.
|
||||||
(
|
(
|
||||||
&TestKind::Switch { adt_def: tested_adt_def, .. },
|
&TestKind::Switch { adt_def: tested_adt_def },
|
||||||
&TestCase::Variant { adt_def, variant_index },
|
&TestCase::Variant { adt_def, variant_index },
|
||||||
) => {
|
) => {
|
||||||
assert_eq!(adt_def, tested_adt_def);
|
assert_eq!(adt_def, tested_adt_def);
|
||||||
|
@ -581,17 +507,19 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
//
|
//
|
||||||
// FIXME(#29623) we could use PatKind::Range to rule
|
// FIXME(#29623) we could use PatKind::Range to rule
|
||||||
// things out here, in some cases.
|
// things out here, in some cases.
|
||||||
(TestKind::SwitchInt { options }, &TestCase::Constant { value })
|
(TestKind::SwitchInt, &TestCase::Constant { value })
|
||||||
if is_switch_ty(match_pair.pattern.ty) =>
|
if is_switch_ty(match_pair.pattern.ty) =>
|
||||||
{
|
{
|
||||||
fully_matched = true;
|
fully_matched = true;
|
||||||
let bits = options.get(&value).unwrap();
|
let bits = value.eval_bits(self.tcx, self.param_env);
|
||||||
Some(TestBranch::Constant(value, *bits))
|
Some(TestBranch::Constant(value, bits))
|
||||||
}
|
}
|
||||||
(TestKind::SwitchInt { options }, TestCase::Range(range)) => {
|
(TestKind::SwitchInt, TestCase::Range(range)) => {
|
||||||
fully_matched = false;
|
fully_matched = false;
|
||||||
let not_contained =
|
let not_contained =
|
||||||
self.values_not_contained_in_range(&*range, options).unwrap_or(false);
|
sorted_candidates.keys().filter_map(|br| br.as_constant()).copied().all(
|
||||||
|
|val| matches!(range.contains(val, self.tcx, self.param_env), Some(false)),
|
||||||
|
);
|
||||||
|
|
||||||
not_contained.then(|| {
|
not_contained.then(|| {
|
||||||
// No switch values are contained in the pattern range,
|
// No switch values are contained in the pattern range,
|
||||||
|
@ -732,20 +660,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
fn values_not_contained_in_range(
|
|
||||||
&self,
|
|
||||||
range: &PatRange<'tcx>,
|
|
||||||
options: &FxIndexMap<Const<'tcx>, u128>,
|
|
||||||
) -> Option<bool> {
|
|
||||||
for &val in options.keys() {
|
|
||||||
if range.contains(val, self.tcx, self.param_env)? {
|
|
||||||
return Some(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_switch_ty(ty: Ty<'_>) -> bool {
|
fn is_switch_ty(ty: Ty<'_>) -> bool {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue