Skip to content

Commit 33a06b7

Browse files
committed
Add reachable_patterns lint to rfc-2008-non_exhaustive
Add linting on non_exhaustive structs and enum variants Add ui tests for non_exhaustive reachable lint Rename to non_exhaustive_omitted_patterns and avoid triggering on if let
1 parent c3c0f80 commit 33a06b7

File tree

10 files changed

+626
-68
lines changed

10 files changed

+626
-68
lines changed

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+
}

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

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)