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