Skip to content

Commit 2d3dcfa

Browse files
authored
Rollup merge of #121823 - Nadrieril:never-witnesses, r=compiler-errors
never patterns: suggest `!` patterns on non-exhaustive matches When a match is non-exhaustive we now suggest never patterns whenever it makes sense. r? ``@compiler-errors``
2 parents 05f7633 + 1b31e14 commit 2d3dcfa

File tree

11 files changed

+982
-242
lines changed

11 files changed

+982
-242
lines changed

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

+39-35
Original file line numberDiff line numberDiff line change
@@ -938,43 +938,30 @@ fn report_non_exhaustive_match<'p, 'tcx>(
938938
};
939939
// In the case of an empty match, replace the '`_` not covered' diagnostic with something more
940940
// informative.
941-
let mut err;
942-
let pattern;
943-
let patterns_len;
944941
if is_empty_match && !non_empty_enum {
945942
return cx.tcx.dcx().emit_err(NonExhaustivePatternsTypeNotEmpty {
946943
cx,
947944
expr_span,
948945
span: sp,
949946
ty: scrut_ty,
950947
});
951-
} else {
952-
// FIXME: migration of this diagnostic will require list support
953-
let joined_patterns = joined_uncovered_patterns(cx, &witnesses);
954-
err = create_e0004(
955-
cx.tcx.sess,
956-
sp,
957-
format!("non-exhaustive patterns: {joined_patterns} not covered"),
958-
);
959-
err.span_label(
960-
sp,
961-
format!(
962-
"pattern{} {} not covered",
963-
rustc_errors::pluralize!(witnesses.len()),
964-
joined_patterns
965-
),
966-
);
967-
patterns_len = witnesses.len();
968-
pattern = if witnesses.len() < 4 {
969-
witnesses
970-
.iter()
971-
.map(|witness| cx.hoist_witness_pat(witness).to_string())
972-
.collect::<Vec<String>>()
973-
.join(" | ")
974-
} else {
975-
"_".to_string()
976-
};
977-
};
948+
}
949+
950+
// FIXME: migration of this diagnostic will require list support
951+
let joined_patterns = joined_uncovered_patterns(cx, &witnesses);
952+
let mut err = create_e0004(
953+
cx.tcx.sess,
954+
sp,
955+
format!("non-exhaustive patterns: {joined_patterns} not covered"),
956+
);
957+
err.span_label(
958+
sp,
959+
format!(
960+
"pattern{} {} not covered",
961+
rustc_errors::pluralize!(witnesses.len()),
962+
joined_patterns
963+
),
964+
);
978965

979966
// Point at the definition of non-covered `enum` variants.
980967
if let Some(AdtDefinedHere { adt_def_span, ty, variants }) =
@@ -1021,6 +1008,23 @@ fn report_non_exhaustive_match<'p, 'tcx>(
10211008
}
10221009
}
10231010

1011+
// Whether we suggest the actual missing patterns or `_`.
1012+
let suggest_the_witnesses = witnesses.len() < 4;
1013+
let suggested_arm = if suggest_the_witnesses {
1014+
let pattern = witnesses
1015+
.iter()
1016+
.map(|witness| cx.hoist_witness_pat(witness).to_string())
1017+
.collect::<Vec<String>>()
1018+
.join(" | ");
1019+
if witnesses.iter().all(|p| p.is_never_pattern()) && cx.tcx.features().never_patterns {
1020+
// Arms with a never pattern don't take a body.
1021+
pattern
1022+
} else {
1023+
format!("{pattern} => todo!()")
1024+
}
1025+
} else {
1026+
format!("_ => todo!()")
1027+
};
10241028
let mut suggestion = None;
10251029
let sm = cx.tcx.sess.source_map();
10261030
match arms {
@@ -1033,7 +1037,7 @@ fn report_non_exhaustive_match<'p, 'tcx>(
10331037
};
10341038
suggestion = Some((
10351039
sp.shrink_to_hi().with_hi(expr_span.hi()),
1036-
format!(" {{{indentation}{more}{pattern} => todo!(),{indentation}}}",),
1040+
format!(" {{{indentation}{more}{suggested_arm},{indentation}}}",),
10371041
));
10381042
}
10391043
[only] => {
@@ -1059,7 +1063,7 @@ fn report_non_exhaustive_match<'p, 'tcx>(
10591063
};
10601064
suggestion = Some((
10611065
only.span.shrink_to_hi(),
1062-
format!("{comma}{pre_indentation}{pattern} => todo!()"),
1066+
format!("{comma}{pre_indentation}{suggested_arm}"),
10631067
));
10641068
}
10651069
[.., prev, last] => {
@@ -1082,7 +1086,7 @@ fn report_non_exhaustive_match<'p, 'tcx>(
10821086
if let Some(spacing) = spacing {
10831087
suggestion = Some((
10841088
last.span.shrink_to_hi(),
1085-
format!("{comma}{spacing}{pattern} => todo!()"),
1089+
format!("{comma}{spacing}{suggested_arm}"),
10861090
));
10871091
}
10881092
}
@@ -1093,13 +1097,13 @@ fn report_non_exhaustive_match<'p, 'tcx>(
10931097
let msg = format!(
10941098
"ensure that all possible cases are being handled by adding a match arm with a wildcard \
10951099
pattern{}{}",
1096-
if patterns_len > 1 && patterns_len < 4 && suggestion.is_some() {
1100+
if witnesses.len() > 1 && suggest_the_witnesses && suggestion.is_some() {
10971101
", a match arm with multiple or-patterns"
10981102
} else {
10991103
// we are either not suggesting anything, or suggesting `_`
11001104
""
11011105
},
1102-
match patterns_len {
1106+
match witnesses.len() {
11031107
// non-exhaustive enum case
11041108
0 if suggestion.is_some() => " as shown",
11051109
0 => "",

compiler/rustc_pattern_analysis/src/constructor.rs

+33-6
Original file line numberDiff line numberDiff line change
@@ -678,15 +678,19 @@ pub enum Constructor<Cx: PatCx> {
678678
Or,
679679
/// Wildcard pattern.
680680
Wildcard,
681+
/// Never pattern. Only used in `WitnessPat`. An actual never pattern should be lowered as
682+
/// `Wildcard`.
683+
Never,
681684
/// Fake extra constructor for enums that aren't allowed to be matched exhaustively. Also used
682-
/// for those types for which we cannot list constructors explicitly, like `f64` and `str`.
685+
/// for those types for which we cannot list constructors explicitly, like `f64` and `str`. Only
686+
/// used in `WitnessPat`.
683687
NonExhaustive,
684-
/// Fake extra constructor for variants that should not be mentioned in diagnostics.
685-
/// We use this for variants behind an unstable gate as well as
686-
/// `#[doc(hidden)]` ones.
688+
/// Fake extra constructor for variants that should not be mentioned in diagnostics. We use this
689+
/// for variants behind an unstable gate as well as `#[doc(hidden)]` ones. Only used in
690+
/// `WitnessPat`.
687691
Hidden,
688692
/// Fake extra constructor for constructors that are not seen in the matrix, as explained at the
689-
/// top of the file.
693+
/// top of the file. Only used for specialization.
690694
Missing,
691695
/// Fake extra constructor that indicates and empty field that is private. When we encounter one
692696
/// we skip the column entirely so we don't observe its emptiness. Only used for specialization.
@@ -708,6 +712,7 @@ impl<Cx: PatCx> Clone for Constructor<Cx> {
708712
Constructor::Str(value) => Constructor::Str(value.clone()),
709713
Constructor::Opaque(inner) => Constructor::Opaque(inner.clone()),
710714
Constructor::Or => Constructor::Or,
715+
Constructor::Never => Constructor::Never,
711716
Constructor::Wildcard => Constructor::Wildcard,
712717
Constructor::NonExhaustive => Constructor::NonExhaustive,
713718
Constructor::Hidden => Constructor::Hidden,
@@ -1040,10 +1045,32 @@ impl<Cx: PatCx> ConstructorSet<Cx> {
10401045
// In a `MaybeInvalid` place even an empty pattern may be reachable. We therefore
10411046
// add a dummy empty constructor here, which will be ignored if the place is
10421047
// `ValidOnly`.
1043-
missing_empty.push(NonExhaustive);
1048+
missing_empty.push(Never);
10441049
}
10451050
}
10461051

10471052
SplitConstructorSet { present, missing, missing_empty }
10481053
}
1054+
1055+
/// Whether this set only contains empty constructors.
1056+
pub(crate) fn all_empty(&self) -> bool {
1057+
match self {
1058+
ConstructorSet::Bool
1059+
| ConstructorSet::Integers { .. }
1060+
| ConstructorSet::Ref
1061+
| ConstructorSet::Union
1062+
| ConstructorSet::Unlistable => false,
1063+
ConstructorSet::NoConstructors => true,
1064+
ConstructorSet::Struct { empty } => *empty,
1065+
ConstructorSet::Variants { variants, non_exhaustive } => {
1066+
!*non_exhaustive
1067+
&& variants
1068+
.iter()
1069+
.all(|visibility| matches!(visibility, VariantVisibility::Empty))
1070+
}
1071+
ConstructorSet::Slice { array_len, subtype_is_empty } => {
1072+
*subtype_is_empty && matches!(array_len, Some(1..))
1073+
}
1074+
}
1075+
}
10491076
}

compiler/rustc_pattern_analysis/src/pat.rs

+18-3
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ impl<Cx: PatCx> fmt::Debug for DeconstructedPat<Cx> {
208208
}
209209
Ok(())
210210
}
211+
Never => write!(f, "!"),
211212
Wildcard | Missing | NonExhaustive | Hidden | PrivateUninhabited => {
212213
write!(f, "_ : {:?}", pat.ty())
213214
}
@@ -311,18 +312,24 @@ impl<Cx: PatCx> WitnessPat<Cx> {
311312
pub(crate) fn new(ctor: Constructor<Cx>, fields: Vec<Self>, ty: Cx::Ty) -> Self {
312313
Self { ctor, fields, ty }
313314
}
314-
pub(crate) fn wildcard(ty: Cx::Ty) -> Self {
315-
Self::new(Wildcard, Vec::new(), ty)
315+
/// Create a wildcard pattern for this type. If the type is empty, we create a `!` pattern.
316+
pub(crate) fn wildcard(cx: &Cx, ty: Cx::Ty) -> Self {
317+
let is_empty = cx.ctors_for_ty(&ty).is_ok_and(|ctors| ctors.all_empty());
318+
let ctor = if is_empty { Never } else { Wildcard };
319+
Self::new(ctor, Vec::new(), ty)
316320
}
317321

318322
/// Construct a pattern that matches everything that starts with this constructor.
319323
/// For example, if `ctor` is a `Constructor::Variant` for `Option::Some`, we get the pattern
320324
/// `Some(_)`.
321325
pub(crate) fn wild_from_ctor(cx: &Cx, ctor: Constructor<Cx>, ty: Cx::Ty) -> Self {
326+
if matches!(ctor, Wildcard) {
327+
return Self::wildcard(cx, ty);
328+
}
322329
let fields = cx
323330
.ctor_sub_tys(&ctor, &ty)
324331
.filter(|(_, PrivateUninhabitedField(skip))| !skip)
325-
.map(|(ty, _)| Self::wildcard(ty))
332+
.map(|(ty, _)| Self::wildcard(cx, ty))
326333
.collect();
327334
Self::new(ctor, fields, ty)
328335
}
@@ -334,6 +341,14 @@ impl<Cx: PatCx> WitnessPat<Cx> {
334341
&self.ty
335342
}
336343

344+
pub fn is_never_pattern(&self) -> bool {
345+
match self.ctor() {
346+
Never => true,
347+
Or => self.fields.iter().all(|p| p.is_never_pattern()),
348+
_ => self.fields.iter().any(|p| p.is_never_pattern()),
349+
}
350+
}
351+
337352
pub fn iter_fields(&self) -> impl Iterator<Item = &WitnessPat<Cx>> {
338353
self.fields.iter()
339354
}

compiler/rustc_pattern_analysis/src/rustc.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
247247
_ => bug!("bad slice pattern {:?} {:?}", ctor, ty),
248248
},
249249
Bool(..) | IntRange(..) | F32Range(..) | F64Range(..) | Str(..) | Opaque(..)
250-
| NonExhaustive | Hidden | Missing | PrivateUninhabited | Wildcard => &[],
250+
| Never | NonExhaustive | Hidden | Missing | PrivateUninhabited | Wildcard => &[],
251251
Or => {
252252
bug!("called `Fields::wildcards` on an `Or` ctor")
253253
}
@@ -275,7 +275,7 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
275275
Ref => 1,
276276
Slice(slice) => slice.arity(),
277277
Bool(..) | IntRange(..) | F32Range(..) | F64Range(..) | Str(..) | Opaque(..)
278-
| NonExhaustive | Hidden | Missing | PrivateUninhabited | Wildcard => 0,
278+
| Never | NonExhaustive | Hidden | Missing | PrivateUninhabited | Wildcard => 0,
279279
Or => bug!("The `Or` constructor doesn't have a fixed arity"),
280280
}
281281
}
@@ -824,7 +824,8 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
824824
}
825825
}
826826
&Str(value) => PatKind::Constant { value },
827-
Wildcard | NonExhaustive | Hidden | PrivateUninhabited => PatKind::Wild,
827+
Never if self.tcx.features().never_patterns => PatKind::Never,
828+
Never | Wildcard | NonExhaustive | Hidden | PrivateUninhabited => PatKind::Wild,
828829
Missing { .. } => bug!(
829830
"trying to convert a `Missing` constructor into a `Pat`; this is probably a bug,
830831
`Missing` should have been processed in `apply_constructors`"

0 commit comments

Comments
 (0)