Skip to content

Commit ce365bf

Browse files
committed
Auto merge of rust-lang#119835 - Nadrieril:simplify-empty-logic, r=<try>
Exhaustiveness: simplify empty pattern logic The logic that handles empty patterns had gotten quite convoluted. This PR simplifies it a lot. I tried to make the logic as easy as possible to follow; this only does logically equivalent changes. The first commit is a drive-by comment clarification that was requested after another PR a while back. r? `@compiler-errors`
2 parents 533cfde + d95644d commit ce365bf

File tree

4 files changed

+30
-51
lines changed

4 files changed

+30
-51
lines changed

compiler/rustc_mir_build/src/thir/pattern/check_match.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,8 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> {
289289
fn is_known_valid_scrutinee(&self, scrutinee: &Expr<'tcx>) -> bool {
290290
use ExprKind::*;
291291
match &scrutinee.kind {
292-
// Both pointers and references can validly point to a place with invalid data.
292+
// Pointers can validly point to a place with invalid data. It is undecided whether
293+
// references can too, so we conservatively assume they can.
293294
Deref { .. } => false,
294295
// Inherit validity of the parent place, unless the parent is an union.
295296
Field { lhs, .. } => {

compiler/rustc_pattern_analysis/src/constructor.rs

+5-14
Original file line numberDiff line numberDiff line change
@@ -858,12 +858,14 @@ impl<Cx: TypeCx> ConstructorSet<Cx> {
858858
/// any) are missing; 2/ split constructors to handle non-trivial intersections e.g. on ranges
859859
/// or slices. This can get subtle; see [`SplitConstructorSet`] for details of this operation
860860
/// and its invariants.
861-
#[instrument(level = "debug", skip(self, pcx, ctors), ret)]
861+
#[instrument(level = "debug", skip(self, ctors), ret)]
862862
pub(crate) fn split<'a>(
863863
&self,
864-
pcx: &PlaceCtxt<'a, Cx>,
865864
ctors: impl Iterator<Item = &'a Constructor<Cx>> + Clone,
866-
) -> SplitConstructorSet<Cx> {
865+
) -> SplitConstructorSet<Cx>
866+
where
867+
Cx: 'a,
868+
{
867869
let mut present: SmallVec<[_; 1]> = SmallVec::new();
868870
// Empty constructors found missing.
869871
let mut missing_empty = Vec::new();
@@ -1003,17 +1005,6 @@ impl<Cx: TypeCx> ConstructorSet<Cx> {
10031005
}
10041006
}
10051007

1006-
// We have now grouped all the constructors into 3 buckets: present, missing, missing_empty.
1007-
// In the absence of the `exhaustive_patterns` feature however, we don't count nested empty
1008-
// types as empty. Only non-nested `!` or `enum Foo {}` are considered empty.
1009-
if !pcx.mcx.tycx.is_exhaustive_patterns_feature_on()
1010-
&& !(pcx.is_scrutinee && matches!(self, Self::NoConstructors))
1011-
{
1012-
// Treat all missing constructors as nonempty.
1013-
// This clears `missing_empty`.
1014-
missing.append(&mut missing_empty);
1015-
}
1016-
10171008
SplitConstructorSet { present, missing, missing_empty }
10181009
}
10191010
}

compiler/rustc_pattern_analysis/src/lints.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl<'p, 'tcx> PatternColumn<'p, 'tcx> {
5959
) -> Result<SplitConstructorSet<'p, 'tcx>, ErrorGuaranteed> {
6060
let column_ctors = self.patterns.iter().map(|p| p.ctor());
6161
let ctors_for_ty = &pcx.ctors_for_ty()?;
62-
Ok(ctors_for_ty.split(pcx, column_ctors))
62+
Ok(ctors_for_ty.split(column_ctors))
6363
}
6464

6565
/// Does specialization: given a constructor, this takes the patterns from the column that match

compiler/rustc_pattern_analysis/src/usefulness.rs

+22-35
Original file line numberDiff line numberDiff line change
@@ -737,15 +737,13 @@ pub(crate) struct PlaceCtxt<'a, Cx: TypeCx> {
737737
pub(crate) mcx: MatchCtxt<'a, Cx>,
738738
/// Type of the place under investigation.
739739
pub(crate) ty: Cx::Ty,
740-
/// Whether the place is the original scrutinee place, as opposed to a subplace of it.
741-
pub(crate) is_scrutinee: bool,
742740
}
743741

744742
impl<'a, Cx: TypeCx> PlaceCtxt<'a, Cx> {
745743
/// A `PlaceCtxt` when code other than `is_useful` needs one.
746744
#[cfg_attr(not(feature = "rustc"), allow(dead_code))]
747745
pub(crate) fn new_dummy(mcx: MatchCtxt<'a, Cx>, ty: Cx::Ty) -> Self {
748-
PlaceCtxt { mcx, ty, is_scrutinee: false }
746+
PlaceCtxt { mcx, ty }
749747
}
750748

751749
pub(crate) fn ctor_arity(&self, ctor: &Constructor<Cx>) -> usize {
@@ -768,30 +766,16 @@ impl<'a, Cx: TypeCx> PlaceCtxt<'a, Cx> {
768766
pub enum ValidityConstraint {
769767
ValidOnly,
770768
MaybeInvalid,
771-
/// Option for backwards compatibility: the place is not known to be valid but we allow omitting
772-
/// `useful && !reachable` arms anyway.
773-
MaybeInvalidButAllowOmittingArms,
774769
}
775770

776771
impl ValidityConstraint {
777772
pub fn from_bool(is_valid_only: bool) -> Self {
778773
if is_valid_only { ValidOnly } else { MaybeInvalid }
779774
}
780775

781-
fn allow_omitting_side_effecting_arms(self) -> Self {
782-
match self {
783-
MaybeInvalid | MaybeInvalidButAllowOmittingArms => MaybeInvalidButAllowOmittingArms,
784-
// There are no side-effecting empty arms here, nothing to do.
785-
ValidOnly => ValidOnly,
786-
}
787-
}
788-
789776
fn is_known_valid(self) -> bool {
790777
matches!(self, ValidOnly)
791778
}
792-
fn allows_omitting_empty_arms(self) -> bool {
793-
matches!(self, ValidOnly | MaybeInvalidButAllowOmittingArms)
794-
}
795779

796780
/// If the place has validity given by `self` and we read that the value at the place has
797781
/// constructor `ctor`, this computes what we can assume about the validity of the constructor
@@ -814,7 +798,7 @@ impl fmt::Display for ValidityConstraint {
814798
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
815799
let s = match self {
816800
ValidOnly => "✓",
817-
MaybeInvalid | MaybeInvalidButAllowOmittingArms => "?",
801+
MaybeInvalid => "?",
818802
};
819803
write!(f, "{s}")
820804
}
@@ -1456,41 +1440,44 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
14561440
};
14571441

14581442
debug!("ty: {ty:?}");
1459-
let pcx = &PlaceCtxt { mcx, ty, is_scrutinee: is_top_level };
1443+
let pcx = &PlaceCtxt { mcx, ty };
1444+
let ctors_for_ty = pcx.ctors_for_ty()?;
14601445

14611446
// Whether the place/column we are inspecting is known to contain valid data.
14621447
let place_validity = matrix.place_validity[0];
1463-
// For backwards compability we allow omitting some empty arms that we ideally shouldn't.
1464-
let place_validity = place_validity.allow_omitting_side_effecting_arms();
1448+
// We treat match scrutinees of type `!` or `EmptyEnum` differently.
1449+
let is_toplevel_exception =
1450+
is_top_level && matches!(ctors_for_ty, ConstructorSet::NoConstructors);
1451+
// Whether empty patterns can be omitted for exhaustiveness.
1452+
let can_omit_empty_arms = is_toplevel_exception || mcx.tycx.is_exhaustive_patterns_feature_on();
1453+
// Whether empty patterns are counted as useful or not.
1454+
let empty_arms_are_unreachable = place_validity.is_known_valid() && can_omit_empty_arms;
14651455

14661456
// Analyze the constructors present in this column.
14671457
let ctors = matrix.heads().map(|p| p.ctor());
1468-
let ctors_for_ty = pcx.ctors_for_ty()?;
1469-
let is_integers = matches!(ctors_for_ty, ConstructorSet::Integers { .. }); // For diagnostics.
1470-
let split_set = ctors_for_ty.split(pcx, ctors);
1458+
let mut split_set = ctors_for_ty.split(ctors);
14711459
let all_missing = split_set.present.is_empty();
1472-
14731460
// Build the set of constructors we will specialize with. It must cover the whole type.
1461+
// We need to iterate over a full set of constructors, so we add `Missing` to represent the
1462+
// missing ones. This is explained under "Constructor Splitting" at the top of this file.
14741463
let mut split_ctors = split_set.present;
1475-
if !split_set.missing.is_empty() {
1476-
// We need to iterate over a full set of constructors, so we add `Missing` to represent the
1477-
// missing ones. This is explained under "Constructor Splitting" at the top of this file.
1478-
split_ctors.push(Constructor::Missing);
1479-
} else if !split_set.missing_empty.is_empty() && !place_validity.is_known_valid() {
1480-
// The missing empty constructors are reachable if the place can contain invalid data.
1464+
if !(split_set.missing.is_empty()
1465+
&& (split_set.missing_empty.is_empty() || empty_arms_are_unreachable))
1466+
{
14811467
split_ctors.push(Constructor::Missing);
14821468
}
14831469

14841470
// Decide what constructors to report.
1471+
let is_integers = matches!(ctors_for_ty, ConstructorSet::Integers { .. });
14851472
let always_report_all = is_top_level && !is_integers;
14861473
// Whether we should report "Enum::A and Enum::C are missing" or "_ is missing".
14871474
let report_individual_missing_ctors = always_report_all || !all_missing;
14881475
// Which constructors are considered missing. We ensure that `!missing_ctors.is_empty() =>
1489-
// split_ctors.contains(Missing)`. The converse usually holds except in the
1490-
// `MaybeInvalidButAllowOmittingArms` backwards-compatibility case.
1476+
// split_ctors.contains(Missing)`. The converse usually holds except when
1477+
// `!place_validity.is_known_valid()`.
14911478
let mut missing_ctors = split_set.missing;
1492-
if !place_validity.allows_omitting_empty_arms() {
1493-
missing_ctors.extend(split_set.missing_empty);
1479+
if !can_omit_empty_arms {
1480+
missing_ctors.append(&mut split_set.missing_empty);
14941481
}
14951482

14961483
let mut ret = WitnessMatrix::empty();

0 commit comments

Comments
 (0)