Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

never patterns: suggest ! patterns on non-exhaustive matches #121823

Merged
merged 5 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 39 additions & 35 deletions compiler/rustc_mir_build/src/thir/pattern/check_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -939,43 +939,30 @@ fn report_non_exhaustive_match<'p, 'tcx>(
};
// In the case of an empty match, replace the '`_` not covered' diagnostic with something more
// informative.
let mut err;
let pattern;
let patterns_len;
if is_empty_match && !non_empty_enum {
return cx.tcx.dcx().emit_err(NonExhaustivePatternsTypeNotEmpty {
cx,
expr_span,
span: sp,
ty: scrut_ty,
});
} else {
// FIXME: migration of this diagnostic will require list support
let joined_patterns = joined_uncovered_patterns(cx, &witnesses);
err = create_e0004(
cx.tcx.sess,
sp,
format!("non-exhaustive patterns: {joined_patterns} not covered"),
);
err.span_label(
sp,
format!(
"pattern{} {} not covered",
rustc_errors::pluralize!(witnesses.len()),
joined_patterns
),
);
patterns_len = witnesses.len();
pattern = if witnesses.len() < 4 {
witnesses
.iter()
.map(|witness| cx.hoist_witness_pat(witness).to_string())
.collect::<Vec<String>>()
.join(" | ")
} else {
"_".to_string()
};
};
}

// FIXME: migration of this diagnostic will require list support
let joined_patterns = joined_uncovered_patterns(cx, &witnesses);
let mut err = create_e0004(
cx.tcx.sess,
sp,
format!("non-exhaustive patterns: {joined_patterns} not covered"),
);
err.span_label(
sp,
format!(
"pattern{} {} not covered",
rustc_errors::pluralize!(witnesses.len()),
joined_patterns
),
);

// Point at the definition of non-covered `enum` variants.
if let Some(AdtDefinedHere { adt_def_span, ty, variants }) =
Expand Down Expand Up @@ -1022,6 +1009,23 @@ fn report_non_exhaustive_match<'p, 'tcx>(
}
}

// Whether we suggest the actual missing patterns or `_`.
let suggest_the_witnesses = witnesses.len() < 4;
let suggested_arm = if suggest_the_witnesses {
let pattern = witnesses
.iter()
.map(|witness| cx.hoist_witness_pat(witness).to_string())
.collect::<Vec<String>>()
.join(" | ");
if witnesses.iter().all(|p| p.is_never_pattern()) && cx.tcx.features().never_patterns {
// Arms with a never pattern don't take a body.
pattern
} else {
format!("{pattern} => todo!()")
}
} else {
format!("_ => todo!()")
};
let mut suggestion = None;
let sm = cx.tcx.sess.source_map();
match arms {
Expand All @@ -1034,7 +1038,7 @@ fn report_non_exhaustive_match<'p, 'tcx>(
};
suggestion = Some((
sp.shrink_to_hi().with_hi(expr_span.hi()),
format!(" {{{indentation}{more}{pattern} => todo!(),{indentation}}}",),
format!(" {{{indentation}{more}{suggested_arm},{indentation}}}",),
));
}
[only] => {
Expand All @@ -1060,7 +1064,7 @@ fn report_non_exhaustive_match<'p, 'tcx>(
};
suggestion = Some((
only.span.shrink_to_hi(),
format!("{comma}{pre_indentation}{pattern} => todo!()"),
format!("{comma}{pre_indentation}{suggested_arm}"),
));
}
[.., prev, last] => {
Expand All @@ -1083,7 +1087,7 @@ fn report_non_exhaustive_match<'p, 'tcx>(
if let Some(spacing) = spacing {
suggestion = Some((
last.span.shrink_to_hi(),
format!("{comma}{spacing}{pattern} => todo!()"),
format!("{comma}{spacing}{suggested_arm}"),
));
}
}
Expand All @@ -1094,13 +1098,13 @@ fn report_non_exhaustive_match<'p, 'tcx>(
let msg = format!(
"ensure that all possible cases are being handled by adding a match arm with a wildcard \
pattern{}{}",
if patterns_len > 1 && patterns_len < 4 && suggestion.is_some() {
if witnesses.len() > 1 && suggest_the_witnesses && suggestion.is_some() {
", a match arm with multiple or-patterns"
} else {
// we are either not suggesting anything, or suggesting `_`
""
},
match patterns_len {
match witnesses.len() {
// non-exhaustive enum case
0 if suggestion.is_some() => " as shown",
0 => "",
Expand Down
39 changes: 33 additions & 6 deletions compiler/rustc_pattern_analysis/src/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -681,15 +681,19 @@ pub enum Constructor<Cx: TypeCx> {
Or,
/// Wildcard pattern.
Wildcard,
/// Never pattern. Only used in `WitnessPat`. An actual never pattern should be lowered as
/// `Wildcard`.
Never,
/// Fake extra constructor for enums that aren't allowed to be matched exhaustively. Also used
/// for those types for which we cannot list constructors explicitly, like `f64` and `str`.
/// for those types for which we cannot list constructors explicitly, like `f64` and `str`. Only
/// used in `WitnessPat`.
NonExhaustive,
/// Fake extra constructor for variants that should not be mentioned in diagnostics.
/// We use this for variants behind an unstable gate as well as
/// `#[doc(hidden)]` ones.
/// Fake extra constructor for variants that should not be mentioned in diagnostics. We use this
/// for variants behind an unstable gate as well as `#[doc(hidden)]` ones. Only used in
/// `WitnessPat`.
Hidden,
/// Fake extra constructor for constructors that are not seen in the matrix, as explained at the
/// top of the file.
/// top of the file. Only used for specialization.
Missing,
/// Fake extra constructor that indicates and empty field that is private. When we encounter one
/// we skip the column entirely so we don't observe its emptiness. Only used for specialization.
Expand All @@ -711,6 +715,7 @@ impl<Cx: TypeCx> Clone for Constructor<Cx> {
Constructor::Str(value) => Constructor::Str(value.clone()),
Constructor::Opaque(inner) => Constructor::Opaque(inner.clone()),
Constructor::Or => Constructor::Or,
Constructor::Never => Constructor::Never,
Constructor::Wildcard => Constructor::Wildcard,
Constructor::NonExhaustive => Constructor::NonExhaustive,
Constructor::Hidden => Constructor::Hidden,
Expand Down Expand Up @@ -1043,10 +1048,32 @@ impl<Cx: TypeCx> ConstructorSet<Cx> {
// In a `MaybeInvalid` place even an empty pattern may be reachable. We therefore
// add a dummy empty constructor here, which will be ignored if the place is
// `ValidOnly`.
missing_empty.push(NonExhaustive);
missing_empty.push(Never);
}
}

SplitConstructorSet { present, missing, missing_empty }
}

/// Whether this set only contains empty constructors.
pub(crate) fn all_empty(&self) -> bool {
match self {
ConstructorSet::Bool
| ConstructorSet::Integers { .. }
| ConstructorSet::Ref
| ConstructorSet::Union
| ConstructorSet::Unlistable => false,
ConstructorSet::NoConstructors => true,
ConstructorSet::Struct { empty } => *empty,
ConstructorSet::Variants { variants, non_exhaustive } => {
!*non_exhaustive
&& variants
.iter()
.all(|visibility| matches!(visibility, VariantVisibility::Empty))
}
ConstructorSet::Slice { array_len, subtype_is_empty } => {
*subtype_is_empty && matches!(array_len, Some(1..))
}
}
}
}
21 changes: 18 additions & 3 deletions compiler/rustc_pattern_analysis/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ impl<Cx: TypeCx> fmt::Debug for DeconstructedPat<Cx> {
}
Ok(())
}
Never => write!(f, "!"),
Wildcard | Missing | NonExhaustive | Hidden | PrivateUninhabited => {
write!(f, "_ : {:?}", pat.ty())
}
Expand Down Expand Up @@ -291,18 +292,24 @@ impl<Cx: TypeCx> WitnessPat<Cx> {
pub(crate) fn new(ctor: Constructor<Cx>, fields: Vec<Self>, ty: Cx::Ty) -> Self {
Self { ctor, fields, ty }
}
pub(crate) fn wildcard(ty: Cx::Ty) -> Self {
Self::new(Wildcard, Vec::new(), ty)
/// Create a wildcard pattern for this type. If the type is empty, we create a `!` pattern.
pub(crate) fn wildcard(cx: &Cx, ty: Cx::Ty) -> Self {
let is_empty = cx.ctors_for_ty(&ty).is_ok_and(|ctors| ctors.all_empty());
let ctor = if is_empty { Never } else { Wildcard };
Self::new(ctor, Vec::new(), ty)
}

/// Construct a pattern that matches everything that starts with this constructor.
/// For example, if `ctor` is a `Constructor::Variant` for `Option::Some`, we get the pattern
/// `Some(_)`.
pub(crate) fn wild_from_ctor(cx: &Cx, ctor: Constructor<Cx>, ty: Cx::Ty) -> Self {
if matches!(ctor, Wildcard) {
return Self::wildcard(cx, ty);
}
let fields = cx
.ctor_sub_tys(&ctor, &ty)
.filter(|(_, PrivateUninhabitedField(skip))| !skip)
.map(|(ty, _)| Self::wildcard(ty))
.map(|(ty, _)| Self::wildcard(cx, ty))
.collect();
Self::new(ctor, fields, ty)
}
Expand All @@ -314,6 +321,14 @@ impl<Cx: TypeCx> WitnessPat<Cx> {
&self.ty
}

pub fn is_never_pattern(&self) -> bool {
match self.ctor() {
Never => true,
Or => self.fields.iter().all(|p| p.is_never_pattern()),
_ => self.fields.iter().any(|p| p.is_never_pattern()),
}
}

pub fn iter_fields(&self) -> impl Iterator<Item = &WitnessPat<Cx>> {
self.fields.iter()
}
Expand Down
7 changes: 4 additions & 3 deletions compiler/rustc_pattern_analysis/src/rustc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ impl<'p, 'tcx: 'p> RustcMatchCheckCtxt<'p, 'tcx> {
_ => bug!("bad slice pattern {:?} {:?}", ctor, ty),
},
Bool(..) | IntRange(..) | F32Range(..) | F64Range(..) | Str(..) | Opaque(..)
| NonExhaustive | Hidden | Missing | PrivateUninhabited | Wildcard => &[],
| Never | NonExhaustive | Hidden | Missing | PrivateUninhabited | Wildcard => &[],
Or => {
bug!("called `Fields::wildcards` on an `Or` ctor")
}
Expand Down Expand Up @@ -279,7 +279,7 @@ impl<'p, 'tcx: 'p> RustcMatchCheckCtxt<'p, 'tcx> {
Ref => 1,
Slice(slice) => slice.arity(),
Bool(..) | IntRange(..) | F32Range(..) | F64Range(..) | Str(..) | Opaque(..)
| NonExhaustive | Hidden | Missing | PrivateUninhabited | Wildcard => 0,
| Never | NonExhaustive | Hidden | Missing | PrivateUninhabited | Wildcard => 0,
Or => bug!("The `Or` constructor doesn't have a fixed arity"),
}
}
Expand Down Expand Up @@ -809,7 +809,8 @@ impl<'p, 'tcx: 'p> RustcMatchCheckCtxt<'p, 'tcx> {
}
}
&Str(value) => PatKind::Constant { value },
Wildcard | NonExhaustive | Hidden | PrivateUninhabited => PatKind::Wild,
Never if self.tcx.features().never_patterns => PatKind::Never,
Never | Wildcard | NonExhaustive | Hidden | PrivateUninhabited => PatKind::Wild,
Missing { .. } => bug!(
"trying to convert a `Missing` constructor into a `Pat`; this is probably a bug,
`Missing` should have been processed in `apply_constructors`"
Expand Down
Loading
Loading