Skip to content

Commit 24adca0

Browse files
committed
Move lints to their own module
1 parent 3691a0a commit 24adca0

File tree

4 files changed

+347
-302
lines changed

4 files changed

+347
-302
lines changed

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ use rustc_pattern_analysis::constructor::Constructor;
22
use rustc_pattern_analysis::cx::MatchCheckCtxt;
33
use rustc_pattern_analysis::errors::Uncovered;
44
use rustc_pattern_analysis::pat::{DeconstructedPat, WitnessPat};
5-
use rustc_pattern_analysis::usefulness::{
6-
compute_match_usefulness, MatchArm, Usefulness, UsefulnessReport,
7-
};
5+
use rustc_pattern_analysis::usefulness::{Usefulness, UsefulnessReport};
6+
use rustc_pattern_analysis::{analyze_match, MatchArm};
87

98
use crate::errors::*;
109

@@ -436,7 +435,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
436435
}
437436

438437
let scrut_ty = scrut.ty;
439-
let report = compute_match_usefulness(&cx, &tarms, scrut_ty);
438+
let report = analyze_match(&cx, &tarms, scrut_ty);
440439

441440
match source {
442441
// Don't report arm reachability of desugared `match $iter.into_iter() { iter => .. }`
@@ -550,7 +549,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
550549
let cx = self.new_cx(refutability, None, scrut, pat.span);
551550
let pat = self.lower_pattern(&cx, pat)?;
552551
let arms = [MatchArm { pat, hir_id: self.lint_level, has_guard: false }];
553-
let report = compute_match_usefulness(&cx, &arms, pat.ty());
552+
let report = analyze_match(&cx, &arms, pat.ty());
554553
Ok((cx, report))
555554
}
556555

compiler/rustc_pattern_analysis/src/lib.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
pub mod constructor;
44
pub mod cx;
55
pub mod errors;
6+
pub(crate) mod lints;
67
pub mod pat;
78
pub mod usefulness;
89

@@ -12,3 +13,44 @@ extern crate tracing;
1213
extern crate rustc_middle;
1314

1415
rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
16+
17+
use lints::PatternColumn;
18+
use rustc_hir::HirId;
19+
use rustc_middle::ty::Ty;
20+
use usefulness::{compute_match_usefulness, UsefulnessReport};
21+
22+
use crate::cx::MatchCheckCtxt;
23+
use crate::lints::{lint_nonexhaustive_missing_variants, lint_overlapping_range_endpoints};
24+
use crate::pat::DeconstructedPat;
25+
26+
/// The arm of a match expression.
27+
#[derive(Clone, Copy, Debug)]
28+
pub struct MatchArm<'p, 'tcx> {
29+
/// The pattern must have been lowered through `check_match::MatchVisitor::lower_pattern`.
30+
pub pat: &'p DeconstructedPat<'p, 'tcx>,
31+
pub hir_id: HirId,
32+
pub has_guard: bool,
33+
}
34+
35+
/// The entrypoint for this crate. Computes whether a match is exhaustive and which of its arms are
36+
/// useful, and runs some lints.
37+
pub fn analyze_match<'p, 'tcx>(
38+
cx: &MatchCheckCtxt<'p, 'tcx>,
39+
arms: &[MatchArm<'p, 'tcx>],
40+
scrut_ty: Ty<'tcx>,
41+
) -> UsefulnessReport<'p, 'tcx> {
42+
let pat_column = PatternColumn::new(arms);
43+
44+
let report = compute_match_usefulness(cx, arms, scrut_ty);
45+
46+
// Lint on ranges that overlap on their endpoints, which is likely a mistake.
47+
lint_overlapping_range_endpoints(cx, &pat_column);
48+
49+
// Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hitting
50+
// `if let`s. Only run if the match is exhaustive otherwise the error is redundant.
51+
if cx.refutable && report.non_exhaustiveness_witnesses.is_empty() {
52+
lint_nonexhaustive_missing_variants(cx, arms, &pat_column, scrut_ty)
53+
}
54+
55+
report
56+
}
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
use smallvec::SmallVec;
2+
3+
use rustc_data_structures::captures::Captures;
4+
use rustc_middle::ty::{self, Ty};
5+
use rustc_session::lint;
6+
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
7+
use rustc_span::Span;
8+
9+
use crate::constructor::{Constructor, IntRange, MaybeInfiniteInt, SplitConstructorSet};
10+
use crate::cx::MatchCheckCtxt;
11+
use crate::errors::{
12+
NonExhaustiveOmittedPattern, NonExhaustiveOmittedPatternLintOnArm, Overlap,
13+
OverlappingRangeEndpoints, Uncovered,
14+
};
15+
use crate::pat::{DeconstructedPat, WitnessPat};
16+
use crate::usefulness::PatCtxt;
17+
use crate::MatchArm;
18+
19+
/// A column of patterns in the matrix, where a column is the intuitive notion of "subpatterns that
20+
/// inspect the same subvalue/place".
21+
/// This is used to traverse patterns column-by-column for lints. Despite similarities with
22+
/// [`compute_exhaustiveness_and_usefulness`], this does a different traversal. Notably this is
23+
/// linear in the depth of patterns, whereas `compute_exhaustiveness_and_usefulness` is worst-case
24+
/// exponential (exhaustiveness is NP-complete). The core difference is that we treat sub-columns
25+
/// separately.
26+
///
27+
/// This must not contain an or-pattern. `specialize` takes care to expand them.
28+
///
29+
/// This is not used in the main algorithm; only in lints.
30+
#[derive(Debug)]
31+
pub(crate) struct PatternColumn<'p, 'tcx> {
32+
patterns: Vec<&'p DeconstructedPat<'p, 'tcx>>,
33+
}
34+
35+
impl<'p, 'tcx> PatternColumn<'p, 'tcx> {
36+
pub(crate) fn new(arms: &[MatchArm<'p, 'tcx>]) -> Self {
37+
let mut patterns = Vec::with_capacity(arms.len());
38+
for arm in arms {
39+
if arm.pat.is_or_pat() {
40+
patterns.extend(arm.pat.flatten_or_pat())
41+
} else {
42+
patterns.push(arm.pat)
43+
}
44+
}
45+
Self { patterns }
46+
}
47+
48+
fn is_empty(&self) -> bool {
49+
self.patterns.is_empty()
50+
}
51+
fn head_ty(&self) -> Option<Ty<'tcx>> {
52+
if self.patterns.len() == 0 {
53+
return None;
54+
}
55+
// If the type is opaque and it is revealed anywhere in the column, we take the revealed
56+
// version. Otherwise we could encounter constructors for the revealed type and crash.
57+
let is_opaque = |ty: Ty<'tcx>| matches!(ty.kind(), ty::Alias(ty::Opaque, ..));
58+
let first_ty = self.patterns[0].ty();
59+
if is_opaque(first_ty) {
60+
for pat in &self.patterns {
61+
let ty = pat.ty();
62+
if !is_opaque(ty) {
63+
return Some(ty);
64+
}
65+
}
66+
}
67+
Some(first_ty)
68+
}
69+
70+
/// Do constructor splitting on the constructors of the column.
71+
fn analyze_ctors(&self, pcx: &PatCtxt<'_, 'p, 'tcx>) -> SplitConstructorSet<'tcx> {
72+
let column_ctors = self.patterns.iter().map(|p| p.ctor());
73+
pcx.cx.ctors_for_ty(pcx.ty).split(pcx, column_ctors)
74+
}
75+
76+
fn iter<'a>(&'a self) -> impl Iterator<Item = &'p DeconstructedPat<'p, 'tcx>> + Captures<'a> {
77+
self.patterns.iter().copied()
78+
}
79+
80+
/// Does specialization: given a constructor, this takes the patterns from the column that match
81+
/// the constructor, and outputs their fields.
82+
/// This returns one column per field of the constructor. They usually all have the same length
83+
/// (the number of patterns in `self` that matched `ctor`), except that we expand or-patterns
84+
/// which may change the lengths.
85+
fn specialize(&self, pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: &Constructor<'tcx>) -> Vec<Self> {
86+
let arity = ctor.arity(pcx);
87+
if arity == 0 {
88+
return Vec::new();
89+
}
90+
91+
// We specialize the column by `ctor`. This gives us `arity`-many columns of patterns. These
92+
// columns may have different lengths in the presence of or-patterns (this is why we can't
93+
// reuse `Matrix`).
94+
let mut specialized_columns: Vec<_> =
95+
(0..arity).map(|_| Self { patterns: Vec::new() }).collect();
96+
let relevant_patterns =
97+
self.patterns.iter().filter(|pat| ctor.is_covered_by(pcx, pat.ctor()));
98+
for pat in relevant_patterns {
99+
let specialized = pat.specialize(pcx, ctor);
100+
for (subpat, column) in specialized.iter().zip(&mut specialized_columns) {
101+
if subpat.is_or_pat() {
102+
column.patterns.extend(subpat.flatten_or_pat())
103+
} else {
104+
column.patterns.push(subpat)
105+
}
106+
}
107+
}
108+
109+
assert!(
110+
!specialized_columns[0].is_empty(),
111+
"ctor {ctor:?} was listed as present but isn't;
112+
there is an inconsistency between `Constructor::is_covered_by` and `ConstructorSet::split`"
113+
);
114+
specialized_columns
115+
}
116+
}
117+
118+
/// Traverse the patterns to collect any variants of a non_exhaustive enum that fail to be mentioned
119+
/// in a given column.
120+
#[instrument(level = "debug", skip(cx), ret)]
121+
fn collect_nonexhaustive_missing_variants<'p, 'tcx>(
122+
cx: &MatchCheckCtxt<'p, 'tcx>,
123+
column: &PatternColumn<'p, 'tcx>,
124+
) -> Vec<WitnessPat<'tcx>> {
125+
let Some(ty) = column.head_ty() else {
126+
return Vec::new();
127+
};
128+
let pcx = &PatCtxt::new_dummy(cx, ty);
129+
130+
let set = column.analyze_ctors(pcx);
131+
if set.present.is_empty() {
132+
// We can't consistently handle the case where no constructors are present (since this would
133+
// require digging deep through any type in case there's a non_exhaustive enum somewhere),
134+
// so for consistency we refuse to handle the top-level case, where we could handle it.
135+
return vec![];
136+
}
137+
138+
let mut witnesses = Vec::new();
139+
if cx.is_foreign_non_exhaustive_enum(ty) {
140+
witnesses.extend(
141+
set.missing
142+
.into_iter()
143+
// This will list missing visible variants.
144+
.filter(|c| !matches!(c, Constructor::Hidden | Constructor::NonExhaustive))
145+
.map(|missing_ctor| WitnessPat::wild_from_ctor(pcx, missing_ctor)),
146+
)
147+
}
148+
149+
// Recurse into the fields.
150+
for ctor in set.present {
151+
let specialized_columns = column.specialize(pcx, &ctor);
152+
let wild_pat = WitnessPat::wild_from_ctor(pcx, ctor);
153+
for (i, col_i) in specialized_columns.iter().enumerate() {
154+
// Compute witnesses for each column.
155+
let wits_for_col_i = collect_nonexhaustive_missing_variants(cx, col_i);
156+
// For each witness, we build a new pattern in the shape of `ctor(_, _, wit, _, _)`,
157+
// adding enough wildcards to match `arity`.
158+
for wit in wits_for_col_i {
159+
let mut pat = wild_pat.clone();
160+
pat.fields[i] = wit;
161+
witnesses.push(pat);
162+
}
163+
}
164+
}
165+
witnesses
166+
}
167+
168+
pub(crate) fn lint_nonexhaustive_missing_variants<'p, 'tcx>(
169+
cx: &MatchCheckCtxt<'p, 'tcx>,
170+
arms: &[MatchArm<'p, 'tcx>],
171+
pat_column: &PatternColumn<'p, 'tcx>,
172+
scrut_ty: Ty<'tcx>,
173+
) {
174+
if !matches!(
175+
cx.tcx.lint_level_at_node(NON_EXHAUSTIVE_OMITTED_PATTERNS, cx.match_lint_level).0,
176+
rustc_session::lint::Level::Allow
177+
) {
178+
let witnesses = collect_nonexhaustive_missing_variants(cx, pat_column);
179+
if !witnesses.is_empty() {
180+
// Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns`
181+
// is not exhaustive enough.
182+
//
183+
// NB: The partner lint for structs lives in `compiler/rustc_hir_analysis/src/check/pat.rs`.
184+
cx.tcx.emit_spanned_lint(
185+
NON_EXHAUSTIVE_OMITTED_PATTERNS,
186+
cx.match_lint_level,
187+
cx.scrut_span,
188+
NonExhaustiveOmittedPattern {
189+
scrut_ty,
190+
uncovered: Uncovered::new(cx.scrut_span, cx, witnesses),
191+
},
192+
);
193+
}
194+
} else {
195+
// We used to allow putting the `#[allow(non_exhaustive_omitted_patterns)]` on a match
196+
// arm. This no longer makes sense so we warn users, to avoid silently breaking their
197+
// usage of the lint.
198+
for arm in arms {
199+
let (lint_level, lint_level_source) =
200+
cx.tcx.lint_level_at_node(NON_EXHAUSTIVE_OMITTED_PATTERNS, arm.hir_id);
201+
if !matches!(lint_level, rustc_session::lint::Level::Allow) {
202+
let decorator = NonExhaustiveOmittedPatternLintOnArm {
203+
lint_span: lint_level_source.span(),
204+
suggest_lint_on_match: cx.whole_match_span.map(|span| span.shrink_to_lo()),
205+
lint_level: lint_level.as_str(),
206+
lint_name: "non_exhaustive_omitted_patterns",
207+
};
208+
209+
use rustc_errors::DecorateLint;
210+
let mut err = cx.tcx.sess.struct_span_warn(arm.pat.span(), "");
211+
err.set_primary_message(decorator.msg());
212+
decorator.decorate_lint(&mut err);
213+
err.emit();
214+
}
215+
}
216+
}
217+
}
218+
219+
/// Traverse the patterns to warn the user about ranges that overlap on their endpoints.
220+
#[instrument(level = "debug", skip(cx))]
221+
pub(crate) fn lint_overlapping_range_endpoints<'p, 'tcx>(
222+
cx: &MatchCheckCtxt<'p, 'tcx>,
223+
column: &PatternColumn<'p, 'tcx>,
224+
) {
225+
let Some(ty) = column.head_ty() else {
226+
return;
227+
};
228+
let pcx = &PatCtxt::new_dummy(cx, ty);
229+
230+
let set = column.analyze_ctors(pcx);
231+
232+
if matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_)) {
233+
let emit_lint = |overlap: &IntRange, this_span: Span, overlapped_spans: &[Span]| {
234+
let overlap_as_pat = cx.hoist_pat_range(overlap, ty);
235+
let overlaps: Vec<_> = overlapped_spans
236+
.iter()
237+
.copied()
238+
.map(|span| Overlap { range: overlap_as_pat.clone(), span })
239+
.collect();
240+
cx.tcx.emit_spanned_lint(
241+
lint::builtin::OVERLAPPING_RANGE_ENDPOINTS,
242+
cx.match_lint_level,
243+
this_span,
244+
OverlappingRangeEndpoints { overlap: overlaps, range: this_span },
245+
);
246+
};
247+
248+
// If two ranges overlapped, the split set will contain their intersection as a singleton.
249+
let split_int_ranges = set.present.iter().filter_map(|c| c.as_int_range());
250+
for overlap_range in split_int_ranges.clone() {
251+
if overlap_range.is_singleton() {
252+
let overlap: MaybeInfiniteInt = overlap_range.lo;
253+
// Ranges that look like `lo..=overlap`.
254+
let mut prefixes: SmallVec<[_; 1]> = Default::default();
255+
// Ranges that look like `overlap..=hi`.
256+
let mut suffixes: SmallVec<[_; 1]> = Default::default();
257+
// Iterate on patterns that contained `overlap`.
258+
for pat in column.iter() {
259+
let this_span = pat.span();
260+
let Constructor::IntRange(this_range) = pat.ctor() else { continue };
261+
if this_range.is_singleton() {
262+
// Don't lint when one of the ranges is a singleton.
263+
continue;
264+
}
265+
if this_range.lo == overlap {
266+
// `this_range` looks like `overlap..=this_range.hi`; it overlaps with any
267+
// ranges that look like `lo..=overlap`.
268+
if !prefixes.is_empty() {
269+
emit_lint(overlap_range, this_span, &prefixes);
270+
}
271+
suffixes.push(this_span)
272+
} else if this_range.hi == overlap.plus_one() {
273+
// `this_range` looks like `this_range.lo..=overlap`; it overlaps with any
274+
// ranges that look like `overlap..=hi`.
275+
if !suffixes.is_empty() {
276+
emit_lint(overlap_range, this_span, &suffixes);
277+
}
278+
prefixes.push(this_span)
279+
}
280+
}
281+
}
282+
}
283+
} else {
284+
// Recurse into the fields.
285+
for ctor in set.present {
286+
for col in column.specialize(pcx, &ctor) {
287+
lint_overlapping_range_endpoints(cx, &col);
288+
}
289+
}
290+
}
291+
}

0 commit comments

Comments
 (0)