Skip to content

Commit 2b5ddf3

Browse files
committed
Auto merge of #86809 - DevinR528:reachable-pat, r=Nadrieril
Add non_exhaustive_omitted_patterns lint related to rfc-2008-non_exhaustive Fixes: #84332 This PR adds `non_exhaustive_omitted_patterns`, an allow by default lint that is triggered when a `non_exhaustive` type is missing explicit patterns. The warning or deny attribute can be put above the wildcard `_` pattern on enums or on the expression for enums or structs. The lint is capable of warning about multiple types within the same pattern. This lint will not be triggered for `if let ..` patterns. ```rust // crate A #[non_exhaustive] pub struct Foo { a: u8, b: usize, } #[non_exhaustive] pub enum Bar { A(Foo), B, } // crate B #[deny(non_exhaustive_omitted_patterns)] // here match Bar::B { Bar::B => {} #[deny(non_exhaustive_omitted_patterns)] // or here _ => {} } #[warn(non_exhaustive_omitted_patterns)] // only here let Foo { a, .. } = Foo::default(); #[deny(non_exhaustive_omitted_patterns)] match Bar::B { // triggers for Bar::B, and Foo.b Bar::A(Foo { a, .. }) => {} // if the attribute was here only Bar::B would cause a warning _ => {} } ```
2 parents 34327f6 + 33a06b7 commit 2b5ddf3

File tree

10 files changed

+626
-68
lines changed

10 files changed

+626
-68
lines changed

Diff for: compiler/rustc_lint_defs/src/builtin.rs

+54
Original file line numberDiff line numberDiff line change
@@ -3010,6 +3010,7 @@ declare_lint_pass! {
30103010
UNSUPPORTED_CALLING_CONVENTIONS,
30113011
BREAK_WITH_LABEL_AND_LOOP,
30123012
UNUSED_ATTRIBUTES,
3013+
NON_EXHAUSTIVE_OMITTED_PATTERNS,
30133014
]
30143015
}
30153016

@@ -3416,3 +3417,56 @@ declare_lint! {
34163417
Warn,
34173418
"`break` expression with label and unlabeled loop as value expression"
34183419
}
3420+
3421+
declare_lint! {
3422+
/// The `non_exhaustive_omitted_patterns` lint detects when a wildcard (`_` or `..`) in a
3423+
/// pattern for a `#[non_exhaustive]` struct or enum is reachable.
3424+
///
3425+
/// ### Example
3426+
///
3427+
/// ```rust,ignore (needs separate crate)
3428+
/// // crate A
3429+
/// #[non_exhaustive]
3430+
/// pub enum Bar {
3431+
/// A,
3432+
/// B, // added variant in non breaking change
3433+
/// }
3434+
///
3435+
/// // in crate B
3436+
/// match Bar::A {
3437+
/// Bar::A => {},
3438+
/// #[warn(non_exhaustive_omitted_patterns)]
3439+
/// _ => {},
3440+
/// }
3441+
/// ```
3442+
///
3443+
/// This will produce:
3444+
///
3445+
/// ```text
3446+
/// warning: reachable patterns not covered of non exhaustive enum
3447+
/// --> $DIR/reachable-patterns.rs:70:9
3448+
/// |
3449+
/// LL | _ => {}
3450+
/// | ^ pattern `B` not covered
3451+
/// |
3452+
/// note: the lint level is defined here
3453+
/// --> $DIR/reachable-patterns.rs:69:16
3454+
/// |
3455+
/// LL | #[warn(non_exhaustive_omitted_patterns)]
3456+
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3457+
/// = help: ensure that all possible cases are being handled by adding the suggested match arms
3458+
/// = note: the matched value is of type `Bar` and the `non_exhaustive_omitted_patterns` attribute was found
3459+
/// ```
3460+
///
3461+
/// ### Explanation
3462+
///
3463+
/// Structs and enums tagged with `#[non_exhaustive]` force the user to add a
3464+
/// (potentially redundant) wildcard when pattern-matching, to allow for future
3465+
/// addition of fields or variants. The `non_exhaustive_omitted_patterns` lint
3466+
/// detects when such a wildcard happens to actually catch some fields/variants.
3467+
/// In other words, when the match without the wildcard would not be exhaustive.
3468+
/// This lets the user be informed if new fields/variants were added.
3469+
pub NON_EXHAUSTIVE_OMITTED_PATTERNS,
3470+
Allow,
3471+
"detect when patterns of types marked `non_exhaustive` are missed",
3472+
}

Diff for: compiler/rustc_mir_build/src/thir/pattern/check_match.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
1414
use rustc_hir::{HirId, Pat};
1515
use rustc_middle::thir::PatKind;
1616
use rustc_middle::ty::{self, Ty, TyCtxt};
17-
use rustc_session::lint::builtin::BINDINGS_WITH_VARIANT_NAME;
18-
use rustc_session::lint::builtin::{IRREFUTABLE_LET_PATTERNS, UNREACHABLE_PATTERNS};
17+
use rustc_session::lint::builtin::{
18+
BINDINGS_WITH_VARIANT_NAME, IRREFUTABLE_LET_PATTERNS, UNREACHABLE_PATTERNS,
19+
};
1920
use rustc_session::Session;
2021
use rustc_span::{DesugaringKind, ExpnKind, Span};
2122
use std::slice;
@@ -559,7 +560,7 @@ fn non_exhaustive_match<'p, 'tcx>(
559560
err.emit();
560561
}
561562

562-
fn joined_uncovered_patterns(witnesses: &[super::Pat<'_>]) -> String {
563+
crate fn joined_uncovered_patterns(witnesses: &[super::Pat<'_>]) -> String {
563564
const LIMIT: usize = 3;
564565
match witnesses {
565566
[] => bug!(),
@@ -576,7 +577,7 @@ fn joined_uncovered_patterns(witnesses: &[super::Pat<'_>]) -> String {
576577
}
577578
}
578579

579-
fn pattern_not_covered_label(witnesses: &[super::Pat<'_>], joined_patterns: &str) -> String {
580+
crate fn pattern_not_covered_label(witnesses: &[super::Pat<'_>], joined_patterns: &str) -> String {
580581
format!("pattern{} {} not covered", rustc_errors::pluralize!(witnesses.len()), joined_patterns)
581582
}
582583

Diff for: compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs

+44-14
Original file line numberDiff line numberDiff line change
@@ -606,8 +606,9 @@ pub(super) enum Constructor<'tcx> {
606606
/// for those types for which we cannot list constructors explicitly, like `f64` and `str`.
607607
NonExhaustive,
608608
/// Stands for constructors that are not seen in the matrix, as explained in the documentation
609-
/// for [`SplitWildcard`].
610-
Missing,
609+
/// for [`SplitWildcard`]. The carried `bool` is used for the `non_exhaustive_omitted_patterns`
610+
/// lint.
611+
Missing { nonexhaustive_enum_missing_real_variants: bool },
611612
/// Wildcard pattern.
612613
Wildcard,
613614
}
@@ -617,6 +618,10 @@ impl<'tcx> Constructor<'tcx> {
617618
matches!(self, Wildcard)
618619
}
619620

621+
pub(super) fn is_non_exhaustive(&self) -> bool {
622+
matches!(self, NonExhaustive)
623+
}
624+
620625
fn as_int_range(&self) -> Option<&IntRange> {
621626
match self {
622627
IntRange(range) => Some(range),
@@ -756,7 +761,7 @@ impl<'tcx> Constructor<'tcx> {
756761
// Wildcards cover anything
757762
(_, Wildcard) => true,
758763
// The missing ctors are not covered by anything in the matrix except wildcards.
759-
(Missing | Wildcard, _) => false,
764+
(Missing { .. } | Wildcard, _) => false,
760765

761766
(Single, Single) => true,
762767
(Variant(self_id), Variant(other_id)) => self_id == other_id,
@@ -829,7 +834,7 @@ impl<'tcx> Constructor<'tcx> {
829834
.any(|other| slice.is_covered_by(other)),
830835
// This constructor is never covered by anything else
831836
NonExhaustive => false,
832-
Str(..) | FloatRange(..) | Opaque | Missing | Wildcard => {
837+
Str(..) | FloatRange(..) | Opaque | Missing { .. } | Wildcard => {
833838
span_bug!(pcx.span, "found unexpected ctor in all_ctors: {:?}", self)
834839
}
835840
}
@@ -919,8 +924,14 @@ impl<'tcx> SplitWildcard<'tcx> {
919924
&& !cx.tcx.features().exhaustive_patterns
920925
&& !pcx.is_top_level;
921926

922-
if is_secretly_empty || is_declared_nonexhaustive {
927+
if is_secretly_empty {
923928
smallvec![NonExhaustive]
929+
} else if is_declared_nonexhaustive {
930+
def.variants
931+
.indices()
932+
.map(|idx| Variant(idx))
933+
.chain(Some(NonExhaustive))
934+
.collect()
924935
} else if cx.tcx.features().exhaustive_patterns {
925936
// If `exhaustive_patterns` is enabled, we exclude variants known to be
926937
// uninhabited.
@@ -975,6 +986,7 @@ impl<'tcx> SplitWildcard<'tcx> {
975986
// This type is one for which we cannot list constructors, like `str` or `f64`.
976987
_ => smallvec![NonExhaustive],
977988
};
989+
978990
SplitWildcard { matrix_ctors: Vec::new(), all_ctors }
979991
}
980992

@@ -1039,7 +1051,17 @@ impl<'tcx> SplitWildcard<'tcx> {
10391051
// sometimes prefer reporting the list of constructors instead of just `_`.
10401052
let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty);
10411053
let ctor = if !self.matrix_ctors.is_empty() || report_when_all_missing {
1042-
Missing
1054+
if pcx.is_non_exhaustive {
1055+
Missing {
1056+
nonexhaustive_enum_missing_real_variants: self
1057+
.iter_missing(pcx)
1058+
.filter(|c| !c.is_non_exhaustive())
1059+
.next()
1060+
.is_some(),
1061+
}
1062+
} else {
1063+
Missing { nonexhaustive_enum_missing_real_variants: false }
1064+
}
10431065
} else {
10441066
Wildcard
10451067
};
@@ -1176,7 +1198,12 @@ impl<'p, 'tcx> Fields<'p, 'tcx> {
11761198
}
11771199
_ => bug!("bad slice pattern {:?} {:?}", constructor, ty),
11781200
},
1179-
Str(..) | FloatRange(..) | IntRange(..) | NonExhaustive | Opaque | Missing
1201+
Str(..)
1202+
| FloatRange(..)
1203+
| IntRange(..)
1204+
| NonExhaustive
1205+
| Opaque
1206+
| Missing { .. }
11801207
| Wildcard => Fields::Slice(&[]),
11811208
};
11821209
debug!("Fields::wildcards({:?}, {:?}) = {:#?}", constructor, ty, ret);
@@ -1189,15 +1216,18 @@ impl<'p, 'tcx> Fields<'p, 'tcx> {
11891216
/// This is roughly the inverse of `specialize_constructor`.
11901217
///
11911218
/// Examples:
1192-
/// `ctor`: `Constructor::Single`
1193-
/// `ty`: `Foo(u32, u32, u32)`
1194-
/// `self`: `[10, 20, _]`
1219+
///
1220+
/// ```text
1221+
/// ctor: `Constructor::Single`
1222+
/// ty: `Foo(u32, u32, u32)`
1223+
/// self: `[10, 20, _]`
11951224
/// returns `Foo(10, 20, _)`
11961225
///
1197-
/// `ctor`: `Constructor::Variant(Option::Some)`
1198-
/// `ty`: `Option<bool>`
1199-
/// `self`: `[false]`
1226+
/// ctor: `Constructor::Variant(Option::Some)`
1227+
/// ty: `Option<bool>`
1228+
/// self: `[false]`
12001229
/// returns `Some(false)`
1230+
/// ```
12011231
pub(super) fn apply(self, pcx: PatCtxt<'_, 'p, 'tcx>, ctor: &Constructor<'tcx>) -> Pat<'tcx> {
12021232
let subpatterns_and_indices = self.patterns_and_indices();
12031233
let mut subpatterns = subpatterns_and_indices.iter().map(|&(_, p)| p).cloned();
@@ -1265,7 +1295,7 @@ impl<'p, 'tcx> Fields<'p, 'tcx> {
12651295
NonExhaustive => PatKind::Wild,
12661296
Wildcard => return Pat::wildcard_from_ty(pcx.ty),
12671297
Opaque => bug!("we should not try to apply an opaque constructor"),
1268-
Missing => bug!(
1298+
Missing { .. } => bug!(
12691299
"trying to apply the `Missing` constructor; this should have been done in `apply_constructors`"
12701300
),
12711301
};

0 commit comments

Comments
 (0)