1
Fork 0

Clarify handling of hidden variants

This commit is contained in:
Nadrieril 2023-10-03 17:09:20 +02:00
parent c1800ef93f
commit 2f4cab4d21
2 changed files with 75 additions and 73 deletions

View file

@ -609,19 +609,23 @@ pub(super) enum Constructor<'tcx> {
/// boxes for the purposes of exhaustiveness: we must not inspect them, and they /// boxes for the purposes of exhaustiveness: we must not inspect them, and they
/// don't count towards making a match exhaustive. /// don't count towards making a match exhaustive.
Opaque, Opaque,
/// Or-pattern.
Or,
/// Wildcard pattern.
Wildcard,
/// Fake extra constructor for enums that aren't allowed to be matched exhaustively. Also used /// 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`.
NonExhaustive, NonExhaustive,
/// Stands for constructors that are not seen in the matrix, as explained in the code for /// Fake extra constructor for variants that should not be mentioned in diagnostics.
/// [`Constructor::split`]. The carried `bool` is used for the `non_exhaustive_omitted_patterns` /// We use this for variants behind an unstable gate as well as
/// lint. /// `#[doc(hidden)]` ones.
Hidden,
/// Fake extra constructor for constructors that are not seen in the matrix, as explained in the
/// code for [`Constructor::split`]. The carried `bool` is used for the
/// `non_exhaustive_omitted_patterns` lint.
Missing { Missing {
nonexhaustive_enum_missing_real_variants: bool, nonexhaustive_enum_missing_visible_variants: bool,
}, },
/// Wildcard pattern.
Wildcard,
/// Or-pattern.
Or,
} }
impl<'tcx> Constructor<'tcx> { impl<'tcx> Constructor<'tcx> {
@ -652,32 +656,6 @@ impl<'tcx> Constructor<'tcx> {
} }
} }
/// Checks if the `Constructor` is a variant and `TyCtxt::eval_stability` returns
/// `EvalResult::Deny { .. }`.
///
/// This means that the variant has a stdlib unstable feature marking it.
pub(super) fn is_unstable_variant(&self, pcx: &PatCtxt<'_, '_, 'tcx>) -> bool {
if let Constructor::Variant(idx) = self && let ty::Adt(adt, _) = pcx.ty.kind() {
let variant_def_id = adt.variant(*idx).def_id;
// Filter variants that depend on a disabled unstable feature.
return matches!(
pcx.cx.tcx.eval_stability(variant_def_id, None, DUMMY_SP, None),
EvalResult::Deny { .. }
);
}
false
}
/// Checks if the `Constructor` is a `Constructor::Variant` with a `#[doc(hidden)]`
/// attribute from a type not local to the current crate.
pub(super) fn is_doc_hidden_variant(&self, pcx: &PatCtxt<'_, '_, 'tcx>) -> bool {
if let Constructor::Variant(idx) = self && let ty::Adt(adt, _) = pcx.ty.kind() {
let variant_def_id = adt.variants()[*idx].def_id;
return pcx.cx.tcx.is_doc_hidden(variant_def_id) && !variant_def_id.is_local();
}
false
}
fn variant_index_for_adt(&self, adt: ty::AdtDef<'tcx>) -> VariantIdx { fn variant_index_for_adt(&self, adt: ty::AdtDef<'tcx>) -> VariantIdx {
match *self { match *self {
Variant(idx) => idx, Variant(idx) => idx,
@ -713,8 +691,9 @@ impl<'tcx> Constructor<'tcx> {
| F32Range(..) | F32Range(..)
| F64Range(..) | F64Range(..)
| IntRange(..) | IntRange(..)
| NonExhaustive
| Opaque | Opaque
| NonExhaustive
| Hidden
| Missing { .. } | Missing { .. }
| Wildcard => 0, | Wildcard => 0,
Or => bug!("The `Or` constructor doesn't have a fixed arity"), Or => bug!("The `Or` constructor doesn't have a fixed arity"),
@ -795,8 +774,8 @@ impl<'tcx> Constructor<'tcx> {
Wildcard Wildcard
} else { } else {
Missing { Missing {
nonexhaustive_enum_missing_real_variants: split_set nonexhaustive_enum_missing_visible_variants: split_set
.nonexhaustive_enum_missing_real_variants, .nonexhaustive_enum_missing_visible_variants,
} }
}; };
smallvec![ctor] smallvec![ctor]
@ -828,8 +807,8 @@ impl<'tcx> Constructor<'tcx> {
match (self, other) { match (self, other) {
// Wildcards cover anything // Wildcards cover anything
(_, Wildcard) => true, (_, Wildcard) => true,
// The missing ctors are not covered by anything in the matrix except wildcards. // Only a wildcard pattern can match these special constructors.
(Missing { .. } | Wildcard, _) => false, (Wildcard | Missing { .. } | NonExhaustive | Hidden, _) => false,
(Single, Single) => true, (Single, Single) => true,
(Variant(self_id), Variant(other_id)) => self_id == other_id, (Variant(self_id), Variant(other_id)) => self_id == other_id,
@ -860,8 +839,6 @@ impl<'tcx> Constructor<'tcx> {
// We are trying to inspect an opaque constant. Thus we skip the row. // We are trying to inspect an opaque constant. Thus we skip the row.
(Opaque, _) | (_, Opaque) => false, (Opaque, _) | (_, Opaque) => false,
// Only a wildcard pattern can match the special extra constructor.
(NonExhaustive, _) => false,
_ => span_bug!( _ => span_bug!(
pcx.span, pcx.span,
@ -878,7 +855,14 @@ pub(super) enum ConstructorSet {
/// The type has a single constructor, e.g. `&T` or a struct. /// The type has a single constructor, e.g. `&T` or a struct.
Single, Single,
/// This type has the following list of constructors. /// This type has the following list of constructors.
Variants { variants: Vec<VariantIdx>, non_exhaustive: bool }, /// Some variants are hidden, which means they won't be mentioned in diagnostics unless the user
/// mentioned them first. We use this for variants behind an unstable gate as well as
/// `#[doc(hidden)]` ones.
Variants {
visible_variants: Vec<VariantIdx>,
hidden_variants: Vec<VariantIdx>,
non_exhaustive: bool,
},
/// The type is spanned by integer values. The range or ranges give the set of allowed values. /// The type is spanned by integer values. The range or ranges give the set of allowed values.
/// The second range is only useful for `char`. /// The second range is only useful for `char`.
/// This is reused for bool. FIXME: don't. /// This is reused for bool. FIXME: don't.
@ -915,7 +899,7 @@ struct SplitConstructorSet<'tcx> {
present: SmallVec<[Constructor<'tcx>; 1]>, present: SmallVec<[Constructor<'tcx>; 1]>,
missing: Vec<Constructor<'tcx>>, missing: Vec<Constructor<'tcx>>,
/// For the `non_exhaustive_omitted_patterns` lint. /// For the `non_exhaustive_omitted_patterns` lint.
nonexhaustive_enum_missing_real_variants: bool, nonexhaustive_enum_missing_visible_variants: bool,
} }
impl ConstructorSet { impl ConstructorSet {
@ -1001,7 +985,7 @@ impl ConstructorSet {
Self::Uninhabited Self::Uninhabited
} else { } else {
let is_exhaustive_pat_feature = cx.tcx.features().exhaustive_patterns; let is_exhaustive_pat_feature = cx.tcx.features().exhaustive_patterns;
let variants: Vec<_> = def let (hidden_variants, visible_variants) = def
.variants() .variants()
.iter_enumerated() .iter_enumerated()
.filter(|(_, v)| { .filter(|(_, v)| {
@ -1013,9 +997,24 @@ impl ConstructorSet {
.apply(cx.tcx, cx.param_env, cx.module) .apply(cx.tcx, cx.param_env, cx.module)
}) })
.map(|(idx, _)| idx) .map(|(idx, _)| idx)
.collect(); .partition(|idx| {
let variant_def_id = def.variant(*idx).def_id;
// Filter variants that depend on a disabled unstable feature.
let is_unstable = matches!(
cx.tcx.eval_stability(variant_def_id, None, DUMMY_SP, None),
EvalResult::Deny { .. }
);
// Filter foreign `#[doc(hidden)]` variants.
let is_doc_hidden =
cx.tcx.is_doc_hidden(variant_def_id) && !variant_def_id.is_local();
is_unstable || is_doc_hidden
});
Self::Variants { variants, non_exhaustive: is_declared_nonexhaustive } Self::Variants {
visible_variants,
hidden_variants,
non_exhaustive: is_declared_nonexhaustive,
}
} }
} }
ty::Never => Self::Uninhabited, ty::Never => Self::Uninhabited,
@ -1050,7 +1049,7 @@ impl ConstructorSet {
} }
} }
} }
let mut nonexhaustive_enum_missing_real_variants = false; let mut nonexhaustive_enum_missing_visible_variants = false;
match self { match self {
ConstructorSet::Single => { ConstructorSet::Single => {
if seen.is_empty() { if seen.is_empty() {
@ -1059,29 +1058,34 @@ impl ConstructorSet {
present.push(Single); present.push(Single);
} }
} }
ConstructorSet::Variants { variants, non_exhaustive } => { ConstructorSet::Variants { visible_variants, hidden_variants, non_exhaustive } => {
let seen_set: FxHashSet<_> = seen.iter().map(|c| c.as_variant().unwrap()).collect(); let seen_set: FxHashSet<_> = seen.iter().map(|c| c.as_variant().unwrap()).collect();
let mut skipped_a_hidden_variant = false; let mut skipped_a_hidden_variant = false;
for variant in variants { for variant in visible_variants {
let ctor = Variant(*variant); let ctor = Variant(*variant);
if seen_set.contains(&variant) { if seen_set.contains(&variant) {
present.push(ctor); present.push(ctor);
} else if ctor.is_doc_hidden_variant(pcx) || ctor.is_unstable_variant(pcx) {
// We don't want to mention any variants that are `doc(hidden)` or behind an
// unstable feature gate if they aren't present in the match.
skipped_a_hidden_variant = true;
} else { } else {
missing.push(ctor); missing.push(ctor);
} }
} }
nonexhaustive_enum_missing_visible_variants =
*non_exhaustive && !missing.is_empty();
for variant in hidden_variants {
let ctor = Variant(*variant);
if seen_set.contains(&variant) {
present.push(ctor);
} else {
skipped_a_hidden_variant = true;
}
}
if skipped_a_hidden_variant {
missing.push(Hidden);
}
if *non_exhaustive { if *non_exhaustive {
nonexhaustive_enum_missing_real_variants = !missing.is_empty();
missing.push(NonExhaustive); missing.push(NonExhaustive);
} else if skipped_a_hidden_variant {
// FIXME(Nadrieril): This represents the skipped variants, but isn't super
// clean. Using `NonExhaustive` breaks things elsewhere.
missing.push(Wildcard);
} }
} }
ConstructorSet::Integers { range_1, range_2, non_exhaustive } => { ConstructorSet::Integers { range_1, range_2, non_exhaustive } => {
@ -1142,7 +1146,7 @@ impl ConstructorSet {
ConstructorSet::Uninhabited => {} ConstructorSet::Uninhabited => {}
} }
SplitConstructorSet { present, missing, nonexhaustive_enum_missing_real_variants } SplitConstructorSet { present, missing, nonexhaustive_enum_missing_visible_variants }
} }
/// Compute the set of constructors missing from this column. /// Compute the set of constructors missing from this column.
@ -1272,8 +1276,9 @@ impl<'p, 'tcx> Fields<'p, 'tcx> {
| F32Range(..) | F32Range(..)
| F64Range(..) | F64Range(..)
| IntRange(..) | IntRange(..)
| NonExhaustive
| Opaque | Opaque
| NonExhaustive
| Hidden
| Missing { .. } | Missing { .. }
| Wildcard => Fields::empty(), | Wildcard => Fields::empty(),
Or => { Or => {
@ -1587,7 +1592,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> {
} }
&Str(value) => PatKind::Constant { value }, &Str(value) => PatKind::Constant { value },
IntRange(range) => return range.to_pat(cx.tcx, self.ty), IntRange(range) => return range.to_pat(cx.tcx, self.ty),
Wildcard | NonExhaustive => PatKind::Wild, Wildcard | NonExhaustive | Hidden => PatKind::Wild,
Missing { .. } => bug!( Missing { .. } => bug!(
"trying to convert a `Missing` constructor into a `Pat`; this is probably a bug, "trying to convert a `Missing` constructor into a `Pat`; this is probably a bug,
`Missing` should have been processed in `apply_constructors`" `Missing` should have been processed in `apply_constructors`"
@ -1770,15 +1775,15 @@ impl<'p, 'tcx> fmt::Debug for DeconstructedPat<'p, 'tcx> {
F32Range(lo, hi, end) => write!(f, "{lo}{end}{hi}"), F32Range(lo, hi, end) => write!(f, "{lo}{end}{hi}"),
F64Range(lo, hi, end) => write!(f, "{lo}{end}{hi}"), F64Range(lo, hi, end) => write!(f, "{lo}{end}{hi}"),
IntRange(range) => write!(f, "{range:?}"), // Best-effort, will render e.g. `false` as `0..=0` IntRange(range) => write!(f, "{range:?}"), // Best-effort, will render e.g. `false` as `0..=0`
Wildcard | Missing { .. } | NonExhaustive => write!(f, "_ : {:?}", self.ty), Str(value) => write!(f, "{value}"),
Opaque => write!(f, "<constant pattern>"),
Or => { Or => {
for pat in self.iter_fields() { for pat in self.iter_fields() {
write!(f, "{}{:?}", start_or_continue(" | "), pat)?; write!(f, "{}{:?}", start_or_continue(" | "), pat)?;
} }
Ok(()) Ok(())
} }
Str(value) => write!(f, "{value}"), Wildcard | Missing { .. } | NonExhaustive | Hidden => write!(f, "_ : {:?}", self.ty),
Opaque => write!(f, "<constant pattern>"),
} }
} }
} }

View file

@ -872,22 +872,19 @@ fn is_useful<'p, 'tcx>(
&& usefulness.is_useful() && matches!(witness_preference, RealArm) && usefulness.is_useful() && matches!(witness_preference, RealArm)
&& matches!( && matches!(
&ctor, &ctor,
Constructor::Missing { nonexhaustive_enum_missing_real_variants: true } Constructor::Missing { nonexhaustive_enum_missing_visible_variants: true }
) )
{ {
let missing = ConstructorSet::for_ty(pcx.cx, pcx.ty) let missing = ConstructorSet::for_ty(pcx.cx, pcx.ty)
.compute_missing(pcx, matrix.heads().map(DeconstructedPat::ctor)); .compute_missing(pcx, matrix.heads().map(DeconstructedPat::ctor));
// Construct for each missing constructor a "wild" version of this // Construct for each missing constructor a "wild" version of this constructor, that
// constructor, that matches everything that can be built with // matches everything that can be built with it. For example, if `ctor` is a
// it. For example, if `ctor` is a `Constructor::Variant` for // `Constructor::Variant` for `Option::Some`, we get the pattern `Some(_)`.
// `Option::Some`, we get the pattern `Some(_)`.
let patterns = missing let patterns = missing
.into_iter() .into_iter()
// Filter out the `NonExhaustive` because we want to list only real // Because of how we computed `nonexhaustive_enum_missing_visible_variants`,
// variants. Also remove any unstable feature gated variants.
// Because of how we computed `nonexhaustive_enum_missing_real_variants`,
// this will not return an empty `Vec`. // this will not return an empty `Vec`.
.filter(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx))) .filter(|c| !(matches!(c, Constructor::NonExhaustive | Constructor::Hidden)))
.map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor)) .map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor))
.collect::<Vec<_>>(); .collect::<Vec<_>>();