Skip to content

Commit 24eb568

Browse files
committed
Don't warn an empty pattern unreachable if we're not sure the data is valid
1 parent 43b2bbf commit 24eb568

9 files changed

+183
-654
lines changed

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

+26-18
Original file line numberDiff line numberDiff line change
@@ -783,9 +783,6 @@ pub(super) enum Constructor<'tcx> {
783783
}
784784

785785
impl<'tcx> Constructor<'tcx> {
786-
pub(super) fn is_wildcard(&self) -> bool {
787-
matches!(self, Wildcard)
788-
}
789786
pub(super) fn is_non_exhaustive(&self) -> bool {
790787
matches!(self, NonExhaustive)
791788
}
@@ -969,15 +966,17 @@ pub(super) enum ConstructorSet {
969966
/// constructors that exist in the type but are not present in the column.
970967
///
971968
/// More formally, if we discard wildcards from the column, this respects the following constraints:
972-
/// 1. the union of `present` and `missing` covers the whole type
969+
/// 1. the union of `present`, `missing` an `missing_empty` covers all the constructors of the type
973970
/// 2. each constructor in `present` is covered by something in the column
974-
/// 3. no constructor in `missing` is covered by anything in the column
971+
/// 3. no constructor in `missing` or `missing_empty` is covered by anything in the column
975972
/// 4. each constructor in the column is equal to the union of one or more constructors in `present`
976973
/// 5. `missing` does not contain empty constructors (see discussion about emptiness at the top of
977974
/// the file);
978-
/// 6. constructors in `present` and `missing` are split for the column; in other words, they are
979-
/// either fully included in or fully disjoint from each constructor in the column. In other
980-
/// words, there are no non-trivial intersections like between `0..10` and `5..15`.
975+
/// 6. `missing_empty` contains only empty constructors
976+
/// 7. constructors in `present`, `missing` and `missing_empty` are split for the column; in other
977+
/// words, they are either fully included in or fully disjoint from each constructor in the
978+
/// column. In yet other words, there are no non-trivial intersections like between `0..10` and
979+
/// `5..15`.
981980
///
982981
/// We must be particularly careful with weird constructors like `Opaque`: they're not formally part
983982
/// of the `ConstructorSet` for the type, yet if we forgot to include them in `present` we would be
@@ -986,6 +985,7 @@ pub(super) enum ConstructorSet {
986985
pub(super) struct SplitConstructorSet<'tcx> {
987986
pub(super) present: SmallVec<[Constructor<'tcx>; 1]>,
988987
pub(super) missing: Vec<Constructor<'tcx>>,
988+
pub(super) missing_empty: Vec<Constructor<'tcx>>,
989989
}
990990

991991
impl ConstructorSet {
@@ -1134,10 +1134,10 @@ impl ConstructorSet {
11341134
// Constructors in `ctors`, except wildcards and opaques.
11351135
let mut seen = Vec::new();
11361136
for ctor in ctors.cloned() {
1137-
if let Constructor::Opaque(..) = ctor {
1138-
present.push(ctor);
1139-
} else if !ctor.is_wildcard() {
1140-
seen.push(ctor);
1137+
match ctor {
1138+
Opaque(..) => present.push(ctor),
1139+
Wildcard => {} // discard wildcards
1140+
_ => seen.push(ctor),
11411141
}
11421142
}
11431143

@@ -1241,16 +1241,24 @@ impl ConstructorSet {
12411241
missing.push(NonExhaustive);
12421242
}
12431243
ConstructorSet::NoConstructors => {
1244-
if !pcx.is_top_level {
1245-
missing_empty.push(NonExhaustive);
1246-
}
1244+
// In a `MaybeInvalid` place even an empty pattern may be reachable. We therefore
1245+
// add a dummy empty constructor here, which will be ignored if the place is
1246+
// `ValidOnly`.
1247+
missing_empty.push(NonExhaustive);
12471248
}
12481249
}
12491250

1250-
if !pcx.cx.tcx.features().exhaustive_patterns {
1251-
missing.extend(missing_empty);
1251+
// We have now grouped all the constructors into 3 buckets: present, missing, missing_empty.
1252+
// In the absence of the `exhaustive_patterns` feature however, we don't count nested empty
1253+
// types as empty. Only non-nested `!` or `enum Foo {}` are considered empty.
1254+
if !pcx.cx.tcx.features().exhaustive_patterns
1255+
&& !(pcx.is_top_level && matches!(self, Self::NoConstructors))
1256+
{
1257+
// Treat all missing constructors as nonempty.
1258+
missing.extend(missing_empty.drain(..));
12521259
}
1253-
SplitConstructorSet { present, missing }
1260+
1261+
SplitConstructorSet { present, missing, missing_empty }
12541262
}
12551263
}
12561264

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

+64-21
Original file line numberDiff line numberDiff line change
@@ -636,32 +636,56 @@ impl<'a, 'p, 'tcx> fmt::Debug for PatCtxt<'a, 'p, 'tcx> {
636636
}
637637
}
638638

639-
/// In the matrix, tracks whether a given place (aka column) is known to contain a valid value or
640-
/// not.
639+
/// Serves two purposes:
640+
/// - in a wildcard, tracks whether the wildcard matches only valid values (i.e. is a binding `_a`)
641+
/// or also invalid values (i.e. is a true `_` pattern).
642+
/// - in the matrix, track whether a given place (aka column) is known to contain a valid value or
643+
/// not.
641644
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
642645
pub(super) enum ValidityConstraint {
643646
ValidOnly,
644647
MaybeInvalid,
648+
/// Option for backwards compatibility: the place is not known to be valid but we allow omitting
649+
/// `useful && !reachable` arms anyway.
650+
MaybeInvalidButAllowOmittingArms,
645651
}
646652

647653
impl ValidityConstraint {
648654
pub(super) fn from_bool(is_valid_only: bool) -> Self {
649655
if is_valid_only { ValidOnly } else { MaybeInvalid }
650656
}
651657

658+
fn allow_omitting_side_effecting_arms(self) -> Self {
659+
match self {
660+
MaybeInvalid | MaybeInvalidButAllowOmittingArms => MaybeInvalidButAllowOmittingArms,
661+
// There are no side-effecting empty arms here, nothing to do.
662+
ValidOnly => ValidOnly,
663+
}
664+
}
665+
666+
pub(super) fn is_known_valid(self) -> bool {
667+
matches!(self, ValidOnly)
668+
}
669+
pub(super) fn allows_omitting_empty_arms(self) -> bool {
670+
matches!(self, ValidOnly | MaybeInvalidButAllowOmittingArms)
671+
}
672+
652673
/// If the place has validity given by `self` and we read that the value at the place has
653674
/// constructor `ctor`, this computes what we can assume about the validity of the constructor
654675
/// fields.
655676
///
656677
/// Pending further opsem decisions, the current behavior is: validity is preserved, except
657-
/// under `&` where validity is reset to `MaybeInvalid`.
678+
/// inside `&` and union fields where validity is reset to `MaybeInvalid`.
658679
pub(super) fn specialize<'tcx>(
659680
self,
660681
pcx: &PatCtxt<'_, '_, 'tcx>,
661682
ctor: &Constructor<'tcx>,
662683
) -> Self {
663-
// We preserve validity except when we go under a reference.
664-
if matches!(ctor, Constructor::Single) && matches!(pcx.ty.kind(), ty::Ref(..)) {
684+
// We preserve validity except when we go inside a reference or a union field.
685+
if matches!(ctor, Constructor::Single)
686+
&& (matches!(pcx.ty.kind(), ty::Ref(..))
687+
|| matches!(pcx.ty.kind(), ty::Adt(def, ..) if def.is_union()))
688+
{
665689
// Validity of `x: &T` does not imply validity of `*x: T`.
666690
MaybeInvalid
667691
} else {
@@ -674,7 +698,7 @@ impl fmt::Display for ValidityConstraint {
674698
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
675699
let s = match self {
676700
ValidOnly => "✓",
677-
MaybeInvalid => "?",
701+
MaybeInvalid | MaybeInvalidButAllowOmittingArms => "?",
678702
};
679703
write!(f, "{s}")
680704
}
@@ -1197,9 +1221,9 @@ fn compute_exhaustiveness_and_usefulness<'p, 'tcx>(
11971221
for row in matrix.rows_mut() {
11981222
// All rows are useful until they're not.
11991223
row.useful = true;
1224+
// When there's an unguarded row, the match is exhaustive and any subsequent row is not
1225+
// useful.
12001226
if !row.is_under_guard {
1201-
// There's an unguarded row, so the match is exhaustive, and any subsequent row is
1202-
// unreachable.
12031227
return WitnessMatrix::empty();
12041228
}
12051229
}
@@ -1210,26 +1234,37 @@ fn compute_exhaustiveness_and_usefulness<'p, 'tcx>(
12101234
debug!("ty: {ty:?}");
12111235
let pcx = &PatCtxt { cx, ty, is_top_level };
12121236

1237+
// Whether the place/column we are inspecting is known to contain valid data.
1238+
let place_validity = matrix.place_validity[0];
1239+
// For backwards compability we allow omitting some empty arms that we ideally shouldn't.
1240+
let place_validity = place_validity.allow_omitting_side_effecting_arms();
1241+
12131242
// Analyze the constructors present in this column.
12141243
let ctors = matrix.heads().map(|p| p.ctor());
1215-
let split_set = ConstructorSet::for_ty(pcx.cx, pcx.ty).split(pcx, ctors);
1216-
1244+
let split_set = ConstructorSet::for_ty(cx, ty).split(pcx, ctors);
12171245
let all_missing = split_set.present.is_empty();
1218-
let always_report_all = is_top_level && !IntRange::is_integral(pcx.ty);
1219-
// Whether we should report "Enum::A and Enum::C are missing" or "_ is missing".
1220-
let report_individual_missing_ctors = always_report_all || !all_missing;
12211246

1247+
// Build the set of constructors we will specialize with. It must cover the whole type.
12221248
let mut split_ctors = split_set.present;
1223-
let mut only_report_missing = false;
12241249
if !split_set.missing.is_empty() {
12251250
// We need to iterate over a full set of constructors, so we add `Missing` to represent the
12261251
// missing ones. This is explained under "Constructor Splitting" at the top of this file.
12271252
split_ctors.push(Constructor::Missing);
1228-
// For diagnostic purposes we choose to only report the constructors that are missing. Since
1229-
// `Missing` matches only the wildcard rows, it matches fewer rows than any normal
1230-
// constructor and is therefore guaranteed to result in more witnesses. So skipping the
1231-
// other constructors does not jeopardize correctness.
1232-
only_report_missing = true;
1253+
} else if !split_set.missing_empty.is_empty() && !place_validity.is_known_valid() {
1254+
// The missing empty constructors are reachable if the place can contain invalid data.
1255+
split_ctors.push(Constructor::Missing);
1256+
}
1257+
1258+
// Decide what constructors to report.
1259+
let always_report_all = is_top_level && !IntRange::is_integral(pcx.ty);
1260+
// Whether we should report "Enum::A and Enum::C are missing" or "_ is missing".
1261+
let report_individual_missing_ctors = always_report_all || !all_missing;
1262+
// Which constructors are considered missing. We ensure that `!missing_ctors.is_empty() =>
1263+
// split_ctors.contains(Missing)`. The converse usually holds except in the
1264+
// `MaybeInvalidButAllowOmittingArms` backwards-compatibility case.
1265+
let mut missing_ctors = split_set.missing;
1266+
if !place_validity.allows_omitting_empty_arms() {
1267+
missing_ctors.extend(split_set.missing_empty);
12331268
}
12341269

12351270
let mut ret = WitnessMatrix::empty();
@@ -1241,11 +1276,19 @@ fn compute_exhaustiveness_and_usefulness<'p, 'tcx>(
12411276
compute_exhaustiveness_and_usefulness(cx, &mut spec_matrix, false)
12421277
});
12431278

1244-
if !only_report_missing || matches!(ctor, Constructor::Missing) {
1279+
let counts_for_exhaustiveness = match ctor {
1280+
Constructor::Missing => !missing_ctors.is_empty(),
1281+
// If there are missing constructors we'll report those instead. Since `Missing` matches
1282+
// only the wildcard rows, it matches fewer rows than this constructor, and is therefore
1283+
// guaranteed to result in the same or more witnesses. So skipping this does not
1284+
// jeopardize correctness.
1285+
_ => missing_ctors.is_empty(),
1286+
};
1287+
if counts_for_exhaustiveness {
12451288
// Transform witnesses for `spec_matrix` into witnesses for `matrix`.
12461289
witnesses.apply_constructor(
12471290
pcx,
1248-
&split_set.missing,
1291+
&missing_ctors,
12491292
&ctor,
12501293
report_individual_missing_ctors,
12511294
);

0 commit comments

Comments
 (0)