Skip to content

Commit d5edcb1

Browse files
committed
Implement lint as a separate columns-wise pass
1 parent 38e7d42 commit d5edcb1

10 files changed

+334
-200
lines changed

compiler/rustc_lint_defs/src/builtin.rs

+11-10
Original file line numberDiff line numberDiff line change
@@ -3798,8 +3798,10 @@ declare_lint! {
37983798
}
37993799

38003800
declare_lint! {
3801-
/// The `non_exhaustive_omitted_patterns` lint detects when a wildcard (`_` or `..`) in a
3802-
/// pattern for a `#[non_exhaustive]` struct or enum is reachable.
3801+
/// The `non_exhaustive_omitted_patterns` lint detects when some variants or fields of a
3802+
/// `#[non_exhaustive]` struct or enum are not mentioned explicitly in a pattern. This allows
3803+
/// downstream crates to be warned when new variants or fields are added to the upstream struct
3804+
/// or enum.
38033805
///
38043806
/// ### Example
38053807
///
@@ -3827,8 +3829,8 @@ declare_lint! {
38273829
/// warning: reachable patterns not covered of non exhaustive enum
38283830
/// --> $DIR/reachable-patterns.rs:70:9
38293831
/// |
3830-
/// LL | _ => {}
3831-
/// | ^ pattern `B` not covered
3832+
/// LL | match Bar::A {
3833+
/// | ^ pattern `Bar::B` not covered
38323834
/// |
38333835
/// note: the lint level is defined here
38343836
/// --> $DIR/reachable-patterns.rs:69:16
@@ -3841,12 +3843,11 @@ declare_lint! {
38413843
///
38423844
/// ### Explanation
38433845
///
3844-
/// Structs and enums tagged with `#[non_exhaustive]` force the user to add a
3845-
/// (potentially redundant) wildcard when pattern-matching, to allow for future
3846-
/// addition of fields or variants. The `non_exhaustive_omitted_patterns` lint
3847-
/// detects when such a wildcard happens to actually catch some fields/variants.
3848-
/// In other words, when the match without the wildcard would not be exhaustive.
3849-
/// This lets the user be informed if new fields/variants were added.
3846+
/// Structs and enums tagged with `#[non_exhaustive]` force the user to add a (potentially
3847+
/// redundant) wildcard when pattern-matching, to allow for future addition of fields or
3848+
/// variants. The `non_exhaustive_omitted_patterns` lint detects when such a wildcard happens to
3849+
/// actually catch some fields/variants. This lets the user be informed if new fields/variants
3850+
/// were added.
38503851
pub NON_EXHAUSTIVE_OMITTED_PATTERNS,
38513852
Allow,
38523853
"detect when patterns of types marked `non_exhaustive` are missed",

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
250250

251251
let scrut = &self.thir[scrut];
252252
let scrut_ty = scrut.ty;
253-
let report = compute_match_usefulness(&cx, &tarms, self.lint_level, scrut_ty);
253+
let report = compute_match_usefulness(&cx, &tarms, self.lint_level, scrut_ty, scrut.span);
254254

255255
match source {
256256
// Don't report arm reachability of desugared `match $iter.into_iter() { iter => .. }`
@@ -415,7 +415,8 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
415415
let pattern = self.lower_pattern(&mut cx, pat);
416416
let pattern_ty = pattern.ty();
417417
let arm = MatchArm { pat: pattern, hir_id: self.lint_level, has_guard: false };
418-
let report = compute_match_usefulness(&cx, &[arm], self.lint_level, pattern_ty);
418+
let report =
419+
compute_match_usefulness(&cx, &[arm], self.lint_level, pattern_ty, pattern.span());
419420

420421
// Note: we ignore whether the pattern is unreachable (i.e. whether the type is empty). We
421422
// only care about exhaustiveness here.
@@ -583,7 +584,7 @@ fn is_let_irrefutable<'p, 'tcx>(
583584
pat: &'p DeconstructedPat<'p, 'tcx>,
584585
) -> bool {
585586
let arms = [MatchArm { pat, hir_id: pat_id, has_guard: false }];
586-
let report = compute_match_usefulness(&cx, &arms, pat_id, pat.ty());
587+
let report = compute_match_usefulness(&cx, &arms, pat_id, pat.ty(), pat.span());
587588

588589
// Report if the pattern is unreachable, which can only occur when the type is uninhabited.
589590
// This also reports unreachable sub-patterns though, so we can't just replace it with an

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

+24-21
Original file line numberDiff line numberDiff line change
@@ -643,9 +643,8 @@ pub(super) enum Constructor<'tcx> {
643643
/// for those types for which we cannot list constructors explicitly, like `f64` and `str`.
644644
NonExhaustive,
645645
/// Stands for constructors that are not seen in the matrix, as explained in the documentation
646-
/// for [`SplitWildcard`]. The carried `bool` is used for the `non_exhaustive_omitted_patterns`
647-
/// lint.
648-
Missing { nonexhaustive_enum_missing_real_variants: bool },
646+
/// for [`SplitWildcard`].
647+
Missing,
649648
/// Wildcard pattern.
650649
Wildcard,
651650
/// Or-pattern.
@@ -1053,6 +1052,16 @@ impl<'tcx> SplitWildcard<'tcx> {
10531052
self.all_ctors.iter().filter(move |ctor| !ctor.is_covered_by_any(pcx, &self.matrix_ctors))
10541053
}
10551054

1055+
/// Iterate over the constructors for this type that are present in the matrix. This has the
1056+
/// effect of deduplicating present constructors.
1057+
/// WARNING: this omits special constructors like `Wildcard` and `Opaque`.
1058+
pub(super) fn iter_present<'a, 'p>(
1059+
&'a self,
1060+
pcx: &'a PatCtxt<'a, 'p, 'tcx>,
1061+
) -> impl Iterator<Item = &'a Constructor<'tcx>> + Captures<'p> {
1062+
self.all_ctors.iter().filter(move |ctor| ctor.is_covered_by_any(pcx, &self.matrix_ctors))
1063+
}
1064+
10561065
/// Return the set of constructors resulting from splitting the wildcard. As explained at the
10571066
/// top of the file, if any constructors are missing we can ignore the present ones.
10581067
fn into_ctors(self, pcx: &PatCtxt<'_, '_, 'tcx>) -> SmallVec<[Constructor<'tcx>; 1]> {
@@ -1086,15 +1095,7 @@ impl<'tcx> SplitWildcard<'tcx> {
10861095
// sometimes prefer reporting the list of constructors instead of just `_`.
10871096
let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty);
10881097
let ctor = if !self.matrix_ctors.is_empty() || report_when_all_missing {
1089-
if pcx.is_non_exhaustive {
1090-
Missing {
1091-
nonexhaustive_enum_missing_real_variants: self
1092-
.iter_missing(pcx)
1093-
.any(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx))),
1094-
}
1095-
} else {
1096-
Missing { nonexhaustive_enum_missing_real_variants: false }
1097-
}
1098+
Missing
10981099
} else {
10991100
Wildcard
11001101
};
@@ -1240,9 +1241,10 @@ impl<'p, 'tcx> Fields<'p, 'tcx> {
12401241

12411242
/// Values and patterns can be represented as a constructor applied to some fields. This represents
12421243
/// a pattern in this form.
1243-
/// This also keeps track of whether the pattern has been found reachable during analysis. For this
1244-
/// reason we should be careful not to clone patterns for which we care about that. Use
1245-
/// `clone_and_forget_reachability` if you're sure.
1244+
/// This also uses interior mutability to keep track of whether the pattern has been found reachable
1245+
/// during analysis. For this reason we should be careful not to clone patterns for which we care
1246+
/// about that.
1247+
#[derive(Clone)]
12461248
pub(crate) struct DeconstructedPat<'p, 'tcx> {
12471249
ctor: Constructor<'tcx>,
12481250
fields: Fields<'p, 'tcx>,
@@ -1273,12 +1275,6 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> {
12731275
DeconstructedPat::new(ctor, fields, pcx.ty, pcx.span)
12741276
}
12751277

1276-
/// Clone this value. This method emphasizes that cloning loses reachability information and
1277-
/// should be done carefully.
1278-
pub(super) fn clone_and_forget_reachability(&self) -> Self {
1279-
DeconstructedPat::new(self.ctor.clone(), self.fields, self.ty, self.span)
1280-
}
1281-
12821278
pub(crate) fn from_pat(cx: &MatchCheckCtxt<'p, 'tcx>, pat: &Pat<'tcx>) -> Self {
12831279
let mkpat = |pat| DeconstructedPat::from_pat(cx, pat);
12841280
let ctor;
@@ -1522,6 +1518,13 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> {
15221518
pub(super) fn is_or_pat(&self) -> bool {
15231519
matches!(self.ctor, Or)
15241520
}
1521+
pub(super) fn flatten_or_pat(&'p self) -> SmallVec<[&'p Self; 1]> {
1522+
if self.is_or_pat() {
1523+
self.iter_fields().flat_map(|p| p.flatten_or_pat()).collect()
1524+
} else {
1525+
smallvec![self]
1526+
}
1527+
}
15251528

15261529
pub(super) fn ctor(&self) -> &Constructor<'tcx> {
15271530
&self.ctor

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

+123-60
Original file line numberDiff line numberDiff line change
@@ -642,14 +642,7 @@ impl<'p, 'tcx> Usefulness<'p, 'tcx> {
642642
.into_iter()
643643
.flat_map(|witness| {
644644
new_patterns.iter().map(move |pat| {
645-
Witness(
646-
witness
647-
.0
648-
.iter()
649-
.chain(once(pat))
650-
.map(DeconstructedPat::clone_and_forget_reachability)
651-
.collect(),
652-
)
645+
Witness(witness.0.iter().chain(once(pat)).cloned().collect())
653646
})
654647
})
655648
.collect()
@@ -843,7 +836,6 @@ fn is_useful<'p, 'tcx>(
843836
}
844837
// We split the head constructor of `v`.
845838
let split_ctors = v_ctor.split(pcx, matrix.heads().map(DeconstructedPat::ctor));
846-
let is_non_exhaustive_and_wild = is_non_exhaustive && v_ctor.is_wildcard();
847839
// For each constructor, we compute whether there's a value that starts with it that would
848840
// witness the usefulness of `v`.
849841
let start_matrix = &matrix;
@@ -864,56 +856,6 @@ fn is_useful<'p, 'tcx>(
864856
)
865857
});
866858
let usefulness = usefulness.apply_constructor(pcx, start_matrix, &ctor);
867-
868-
// When all the conditions are met we have a match with a `non_exhaustive` enum
869-
// that has the potential to trigger the `non_exhaustive_omitted_patterns` lint.
870-
// To understand the workings checkout `Constructor::split` and `SplitWildcard::new/into_ctors`
871-
if is_non_exhaustive_and_wild
872-
// Only emit a lint on refutable patterns.
873-
&& cx.refutable
874-
// We check that the match has a wildcard pattern and that wildcard is useful,
875-
// meaning there are variants that are covered by the wildcard. Without the check
876-
// for `witness_preference` the lint would trigger on `if let NonExhaustiveEnum::A = foo {}`
877-
&& usefulness.is_useful() && matches!(witness_preference, RealArm)
878-
&& matches!(
879-
&ctor,
880-
Constructor::Missing { nonexhaustive_enum_missing_real_variants: true }
881-
)
882-
{
883-
let patterns = {
884-
let mut split_wildcard = SplitWildcard::new(pcx);
885-
split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor));
886-
// Construct for each missing constructor a "wild" version of this
887-
// constructor, that matches everything that can be built with
888-
// it. For example, if `ctor` is a `Constructor::Variant` for
889-
// `Option::Some`, we get the pattern `Some(_)`.
890-
split_wildcard
891-
.iter_missing(pcx)
892-
// Filter out the `NonExhaustive` because we want to list only real
893-
// variants. Also remove any unstable feature gated variants.
894-
// Because of how we computed `nonexhaustive_enum_missing_real_variants`,
895-
// this will not return an empty `Vec`.
896-
.filter(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx)))
897-
.cloned()
898-
.map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor))
899-
.collect::<Vec<_>>()
900-
};
901-
902-
// Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns`
903-
// is not exhaustive enough.
904-
//
905-
// NB: The partner lint for structs lives in `compiler/rustc_hir_analysis/src/check/pat.rs`.
906-
cx.tcx.emit_spanned_lint(
907-
NON_EXHAUSTIVE_OMITTED_PATTERNS,
908-
lint_root,
909-
pcx.span,
910-
NonExhaustiveOmittedPattern {
911-
scrut_ty: pcx.ty,
912-
uncovered: Uncovered::new(pcx.span, pcx.cx, patterns),
913-
},
914-
);
915-
}
916-
917859
ret.extend(usefulness);
918860
}
919861
}
@@ -925,6 +867,96 @@ fn is_useful<'p, 'tcx>(
925867
ret
926868
}
927869

870+
/// Traverse the patterns to collect any variants of a non_exhaustive enum that fail to be mentioned
871+
/// in a given column. This traverses patterns column-by-column, where a column is the intuitive
872+
/// notion of "subpatterns that inspect the same subvalue".
873+
/// Despite similarities with `is_useful`, this traversal is different. Notably this is linear in the
874+
/// depth of patterns, whereas `is_useful` is worst-case exponential (exhaustiveness is NP-complete).
875+
fn collect_nonexhaustive_missing_variants<'p, 'tcx>(
876+
cx: &MatchCheckCtxt<'p, 'tcx>,
877+
column: &[&DeconstructedPat<'p, 'tcx>],
878+
) -> Vec<DeconstructedPat<'p, 'tcx>> {
879+
let mut witnesses = Vec::new();
880+
// If the column is all wildcards, we don't want to collect anything and we can't recurse
881+
// further.
882+
let is_all_wildcards = column.iter().all(|p| p.ctor().is_wildcard());
883+
if column.is_empty() || is_all_wildcards {
884+
return witnesses;
885+
}
886+
887+
let ty = column[0].ty();
888+
let span = column[0].span();
889+
let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty);
890+
let pcx = &PatCtxt { cx, ty, span, is_top_level: false, is_non_exhaustive };
891+
892+
let mut split_wildcard = SplitWildcard::new(pcx);
893+
split_wildcard.split(pcx, column.iter().map(|p| p.ctor()));
894+
895+
if is_non_exhaustive {
896+
witnesses.extend(
897+
split_wildcard
898+
.iter_missing(pcx)
899+
// Filter out the `NonExhaustive` ctor because we want to list only real variants.
900+
// Also remove any unstable feature gated variants.
901+
.filter(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx)))
902+
.cloned()
903+
.map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor)),
904+
)
905+
}
906+
907+
// Recurse into the fields.
908+
for ctor in split_wildcard.iter_present(pcx) {
909+
let arity = ctor.arity(pcx);
910+
if arity == 0 {
911+
continue;
912+
}
913+
914+
// We specialize the column by `ctor`. This gives us `arity`-many columns of patterns. These
915+
// columns may not have the same length in the presence of or-patterns (this is why we can't
916+
// reuse what `Matrix` does).
917+
let mut specialized_columns: Vec<Vec<_>> = (0..arity).map(|_| Vec::new()).collect();
918+
let relevant_patterns = column.iter().filter(|pat| ctor.is_covered_by(pcx, pat.ctor()));
919+
for pat in relevant_patterns {
920+
let specialized = pat.specialize(pcx, &ctor);
921+
for (subpat, sub_column) in specialized.iter().zip(&mut specialized_columns) {
922+
if subpat.is_or_pat() {
923+
sub_column.extend(subpat.iter_fields())
924+
} else {
925+
sub_column.push(subpat)
926+
}
927+
}
928+
}
929+
assert!(
930+
!specialized_columns[0].is_empty(),
931+
"ctor {ctor:?} was listed as present but isn't"
932+
);
933+
934+
for (i, col_i) in specialized_columns.iter().enumerate() {
935+
// Compute witnesses for each column.
936+
let wits_for_col_i = collect_nonexhaustive_missing_variants(cx, col_i.as_slice());
937+
if wits_for_col_i.is_empty() {
938+
continue;
939+
}
940+
// For each witness, we build a new pattern in the shape of `ctor(_, _, wit, _, _)`,
941+
// adding enough wildcards to match `arity`.
942+
let fields = Fields::wildcards(pcx, &ctor);
943+
for wit in wits_for_col_i {
944+
let fields_with_wit = Fields::from_iter(
945+
cx,
946+
fields
947+
.iter_patterns()
948+
.enumerate()
949+
.map(|(j, wild)| if i == j { &wit } else { wild })
950+
.cloned(),
951+
);
952+
let pat = DeconstructedPat::new(ctor.clone(), fields_with_wit, ty, DUMMY_SP);
953+
witnesses.push(pat);
954+
}
955+
}
956+
}
957+
witnesses
958+
}
959+
928960
/// The arm of a match expression.
929961
#[derive(Clone, Copy, Debug)]
930962
pub(crate) struct MatchArm<'p, 'tcx> {
@@ -965,6 +997,7 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>(
965997
arms: &[MatchArm<'p, 'tcx>],
966998
lint_root: HirId,
967999
scrut_ty: Ty<'tcx>,
1000+
scrut_span: Span,
9681001
) -> UsefulnessReport<'p, 'tcx> {
9691002
let mut matrix = Matrix::empty();
9701003
let arm_usefulness: Vec<_> = arms
@@ -989,9 +1022,39 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>(
9891022
let wild_pattern = cx.pattern_arena.alloc(DeconstructedPat::wildcard(scrut_ty, DUMMY_SP));
9901023
let v = PatStack::from_pattern(wild_pattern);
9911024
let usefulness = is_useful(cx, &matrix, &v, FakeExtraWildcard, lint_root, false, true);
992-
let non_exhaustiveness_witnesses = match usefulness {
1025+
let non_exhaustiveness_witnesses: Vec<_> = match usefulness {
9931026
WithWitnesses(pats) => pats.into_iter().map(|w| w.single_pattern()).collect(),
9941027
NoWitnesses { .. } => bug!(),
9951028
};
1029+
1030+
// Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hittin
1031+
// `if let`s. Only run if the match is exhaustive otherwise the error is redundant.
1032+
if cx.refutable
1033+
&& non_exhaustiveness_witnesses.is_empty()
1034+
&& !matches!(
1035+
cx.tcx.lint_level_at_node(NON_EXHAUSTIVE_OMITTED_PATTERNS, lint_root).0,
1036+
rustc_session::lint::Level::Allow
1037+
)
1038+
{
1039+
let pat_column = arms.iter().flat_map(|arm| arm.pat.flatten_or_pat()).collect::<Vec<_>>();
1040+
let witnesses = collect_nonexhaustive_missing_variants(cx, &pat_column);
1041+
1042+
if !witnesses.is_empty() {
1043+
// Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns`
1044+
// is not exhaustive enough.
1045+
//
1046+
// NB: The partner lint for structs lives in `compiler/rustc_hir_analysis/src/check/pat.rs`.
1047+
cx.tcx.emit_spanned_lint(
1048+
NON_EXHAUSTIVE_OMITTED_PATTERNS,
1049+
lint_root,
1050+
scrut_span,
1051+
NonExhaustiveOmittedPattern {
1052+
scrut_ty,
1053+
uncovered: Uncovered::new(scrut_span, cx, witnesses),
1054+
},
1055+
);
1056+
}
1057+
}
1058+
9961059
UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses }
9971060
}

0 commit comments

Comments
 (0)