mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 14:55:26 +00:00
Auto merge of #116751 - Nadrieril:lint-overlap-per-column, r=davidtwco
Lint overlapping ranges as a separate pass This reworks the [`overlapping_range_endpoints`](https://doc.rust-lang.org/beta/nightly-rustc/rustc_lint_defs/builtin/static.OVERLAPPING_RANGE_ENDPOINTS.html) lint. My motivations are: - It was annoying to have this lint entangled with the exhaustiveness algorithm, especially wrt librarification; - This makes the lint behave consistently. Here's the consistency story. Take the following matches: ```rust match (0u8, true) { (0..=10, true) => {} (10..20, true) => {} (10..20, false) => {} _ => {} } match (true, 0u8) { (true, 0..=10) => {} (true, 10..20) => {} (false, 10..20) => {} _ => {} } ``` There are two semantically consistent options: option 1 we lint all overlaps between the ranges, option 2 we only lint the overlaps that could actually occur (i.e. the ones with `true`). Option 1 is what this PR does. Option 2 is possible but would require the exhaustiveness algorithm to track more things for the sake of the lint. The status quo is that we're inconsistent between the two. Option 1 generates more false postives, but I prefer it from a maintainer's perspective. I do think the difference is minimal; cases where the difference is observable seem rare. This PR adds a separate pass, so this will have a perf impact. Let's see how bad, it looked ok locally.
This commit is contained in:
commit
9d6d5d4894
@ -53,14 +53,13 @@ use smallvec::{smallvec, SmallVec};
|
|||||||
use rustc_apfloat::ieee::{DoubleS, IeeeFloat, SingleS};
|
use rustc_apfloat::ieee::{DoubleS, IeeeFloat, SingleS};
|
||||||
use rustc_data_structures::captures::Captures;
|
use rustc_data_structures::captures::Captures;
|
||||||
use rustc_data_structures::fx::FxHashSet;
|
use rustc_data_structures::fx::FxHashSet;
|
||||||
use rustc_hir::{HirId, RangeEnd};
|
use rustc_hir::RangeEnd;
|
||||||
use rustc_index::Idx;
|
use rustc_index::Idx;
|
||||||
use rustc_middle::middle::stability::EvalResult;
|
use rustc_middle::middle::stability::EvalResult;
|
||||||
use rustc_middle::mir;
|
use rustc_middle::mir;
|
||||||
use rustc_middle::thir::{FieldPat, Pat, PatKind, PatRange};
|
use rustc_middle::thir::{FieldPat, Pat, PatKind, PatRange};
|
||||||
use rustc_middle::ty::layout::IntegerExt;
|
use rustc_middle::ty::layout::IntegerExt;
|
||||||
use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef};
|
use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef};
|
||||||
use rustc_session::lint;
|
|
||||||
use rustc_span::{Span, DUMMY_SP};
|
use rustc_span::{Span, DUMMY_SP};
|
||||||
use rustc_target::abi::{FieldIdx, Integer, VariantIdx, FIRST_VARIANT};
|
use rustc_target::abi::{FieldIdx, Integer, VariantIdx, FIRST_VARIANT};
|
||||||
|
|
||||||
@ -68,7 +67,6 @@ use self::Constructor::*;
|
|||||||
use self::SliceKind::*;
|
use self::SliceKind::*;
|
||||||
|
|
||||||
use super::usefulness::{MatchCheckCtxt, PatCtxt};
|
use super::usefulness::{MatchCheckCtxt, PatCtxt};
|
||||||
use crate::errors::{Overlap, OverlappingRangeEndpoints};
|
|
||||||
|
|
||||||
/// Recursively expand this pattern into its subpatterns. Only useful for or-patterns.
|
/// Recursively expand this pattern into its subpatterns. Only useful for or-patterns.
|
||||||
fn expand_or_pat<'p, 'tcx>(pat: &'p Pat<'tcx>) -> Vec<&'p Pat<'tcx>> {
|
fn expand_or_pat<'p, 'tcx>(pat: &'p Pat<'tcx>) -> Vec<&'p Pat<'tcx>> {
|
||||||
@ -111,15 +109,15 @@ pub(crate) struct IntRange {
|
|||||||
|
|
||||||
impl IntRange {
|
impl IntRange {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_integral(ty: Ty<'_>) -> bool {
|
pub(super) fn is_integral(ty: Ty<'_>) -> bool {
|
||||||
matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_) | ty::Bool)
|
matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_) | ty::Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_singleton(&self) -> bool {
|
pub(super) fn is_singleton(&self) -> bool {
|
||||||
self.range.start() == self.range.end()
|
self.range.start() == self.range.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn boundaries(&self) -> (u128, u128) {
|
pub(super) fn boundaries(&self) -> (u128, u128) {
|
||||||
(*self.range.start(), *self.range.end())
|
(*self.range.start(), *self.range.end())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,23 +175,6 @@ impl IntRange {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn suspicious_intersection(&self, other: &Self) -> bool {
|
|
||||||
// `false` in the following cases:
|
|
||||||
// 1 ---- // 1 ---------- // 1 ---- // 1 ----
|
|
||||||
// 2 ---------- // 2 ---- // 2 ---- // 2 ----
|
|
||||||
//
|
|
||||||
// The following are currently `false`, but could be `true` in the future (#64007):
|
|
||||||
// 1 --------- // 1 ---------
|
|
||||||
// 2 ---------- // 2 ----------
|
|
||||||
//
|
|
||||||
// `true` in the following cases:
|
|
||||||
// 1 ------- // 1 -------
|
|
||||||
// 2 -------- // 2 -------
|
|
||||||
let (lo, hi) = self.boundaries();
|
|
||||||
let (other_lo, other_hi) = other.boundaries();
|
|
||||||
(lo == other_hi || hi == other_lo) && !self.is_singleton() && !other.is_singleton()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Partition a range of integers into disjoint subranges. This does constructor splitting for
|
/// Partition a range of integers into disjoint subranges. This does constructor splitting for
|
||||||
/// integer ranges as explained at the top of the file.
|
/// integer ranges as explained at the top of the file.
|
||||||
///
|
///
|
||||||
@ -293,7 +274,7 @@ impl IntRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Only used for displaying the range.
|
/// Only used for displaying the range.
|
||||||
fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> {
|
pub(super) fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> {
|
||||||
let (lo, hi) = self.boundaries();
|
let (lo, hi) = self.boundaries();
|
||||||
|
|
||||||
let bias = IntRange::signed_bias(tcx, ty);
|
let bias = IntRange::signed_bias(tcx, ty);
|
||||||
@ -315,51 +296,6 @@ impl IntRange {
|
|||||||
|
|
||||||
Pat { ty, span: DUMMY_SP, kind }
|
Pat { ty, span: DUMMY_SP, kind }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lint on likely incorrect range patterns (#63987)
|
|
||||||
pub(super) fn lint_overlapping_range_endpoints<'a, 'p: 'a, 'tcx: 'a>(
|
|
||||||
&self,
|
|
||||||
pcx: &PatCtxt<'_, 'p, 'tcx>,
|
|
||||||
pats: impl Iterator<Item = &'a DeconstructedPat<'p, 'tcx>>,
|
|
||||||
column_count: usize,
|
|
||||||
lint_root: HirId,
|
|
||||||
) {
|
|
||||||
if self.is_singleton() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if column_count != 1 {
|
|
||||||
// FIXME: for now, only check for overlapping ranges on simple range
|
|
||||||
// patterns. Otherwise with the current logic the following is detected
|
|
||||||
// as overlapping:
|
|
||||||
// ```
|
|
||||||
// match (0u8, true) {
|
|
||||||
// (0 ..= 125, false) => {}
|
|
||||||
// (125 ..= 255, true) => {}
|
|
||||||
// _ => {}
|
|
||||||
// }
|
|
||||||
// ```
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let overlap: Vec<_> = pats
|
|
||||||
.filter_map(|pat| Some((pat.ctor().as_int_range()?, pat.span())))
|
|
||||||
.filter(|(range, _)| self.suspicious_intersection(range))
|
|
||||||
.map(|(range, span)| Overlap {
|
|
||||||
range: self.intersection(&range).unwrap().to_pat(pcx.cx.tcx, pcx.ty),
|
|
||||||
span,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if !overlap.is_empty() {
|
|
||||||
pcx.cx.tcx.emit_spanned_lint(
|
|
||||||
lint::builtin::OVERLAPPING_RANGE_ENDPOINTS,
|
|
||||||
lint_root,
|
|
||||||
pcx.span,
|
|
||||||
OverlappingRangeEndpoints { overlap, range: pcx.span },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Note: this is often not what we want: e.g. `false` is converted into the range `0..=0` and
|
/// Note: this is often not what we want: e.g. `false` is converted into the range `0..=0` and
|
||||||
@ -644,7 +580,7 @@ impl<'tcx> Constructor<'tcx> {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn as_int_range(&self) -> Option<&IntRange> {
|
pub(super) fn as_int_range(&self) -> Option<&IntRange> {
|
||||||
match self {
|
match self {
|
||||||
IntRange(range) => Some(range),
|
IntRange(range) => Some(range),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
@ -307,8 +307,10 @@
|
|||||||
|
|
||||||
use self::ArmType::*;
|
use self::ArmType::*;
|
||||||
use self::Usefulness::*;
|
use self::Usefulness::*;
|
||||||
use super::deconstruct_pat::{Constructor, ConstructorSet, DeconstructedPat, WitnessPat};
|
use super::deconstruct_pat::{
|
||||||
use crate::errors::{NonExhaustiveOmittedPattern, Uncovered};
|
Constructor, ConstructorSet, DeconstructedPat, IntRange, SplitConstructorSet, WitnessPat,
|
||||||
|
};
|
||||||
|
use crate::errors::{NonExhaustiveOmittedPattern, Overlap, OverlappingRangeEndpoints, Uncovered};
|
||||||
|
|
||||||
use rustc_data_structures::captures::Captures;
|
use rustc_data_structures::captures::Captures;
|
||||||
|
|
||||||
@ -317,6 +319,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack;
|
|||||||
use rustc_hir::def_id::DefId;
|
use rustc_hir::def_id::DefId;
|
||||||
use rustc_hir::HirId;
|
use rustc_hir::HirId;
|
||||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||||
|
use rustc_session::lint;
|
||||||
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
|
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
|
||||||
use rustc_span::{Span, DUMMY_SP};
|
use rustc_span::{Span, DUMMY_SP};
|
||||||
|
|
||||||
@ -473,11 +476,6 @@ impl<'p, 'tcx> Matrix<'p, 'tcx> {
|
|||||||
Matrix { patterns: vec![] }
|
Matrix { patterns: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Number of columns of this matrix. `None` is the matrix is empty.
|
|
||||||
pub(super) fn column_count(&self) -> Option<usize> {
|
|
||||||
self.patterns.get(0).map(|r| r.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pushes a new row to the matrix. If the row starts with an or-pattern, this recursively
|
/// Pushes a new row to the matrix. If the row starts with an or-pattern, this recursively
|
||||||
/// expands it.
|
/// expands it.
|
||||||
fn push(&mut self, row: PatStack<'p, 'tcx>) {
|
fn push(&mut self, row: PatStack<'p, 'tcx>) {
|
||||||
@ -833,15 +831,6 @@ fn is_useful<'p, 'tcx>(
|
|||||||
|
|
||||||
let v_ctor = v.head().ctor();
|
let v_ctor = v.head().ctor();
|
||||||
debug!(?v_ctor);
|
debug!(?v_ctor);
|
||||||
if let Constructor::IntRange(ctor_range) = &v_ctor {
|
|
||||||
// Lint on likely incorrect range patterns (#63987)
|
|
||||||
ctor_range.lint_overlapping_range_endpoints(
|
|
||||||
pcx,
|
|
||||||
matrix.heads(),
|
|
||||||
matrix.column_count().unwrap_or(0),
|
|
||||||
lint_root,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// We split the head constructor of `v`.
|
// We split the head constructor of `v`.
|
||||||
let split_ctors = v_ctor.split(pcx, matrix.heads().map(DeconstructedPat::ctor));
|
let split_ctors = v_ctor.split(pcx, matrix.heads().map(DeconstructedPat::ctor));
|
||||||
// For each constructor, we compute whether there's a value that starts with it that would
|
// For each constructor, we compute whether there's a value that starts with it that would
|
||||||
@ -875,22 +864,102 @@ fn is_useful<'p, 'tcx>(
|
|||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A column of patterns in the matrix, where a column is the intuitive notion of "subpatterns that
|
||||||
|
/// inspect the same subvalue".
|
||||||
|
/// This is used to traverse patterns column-by-column for lints. Despite similarities with
|
||||||
|
/// `is_useful`, this is a different traversal. Notably this is linear in the depth of patterns,
|
||||||
|
/// whereas `is_useful` is worst-case exponential (exhaustiveness is NP-complete).
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct PatternColumn<'p, 'tcx> {
|
||||||
|
patterns: Vec<&'p DeconstructedPat<'p, 'tcx>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'p, 'tcx> PatternColumn<'p, 'tcx> {
|
||||||
|
fn new(patterns: Vec<&'p DeconstructedPat<'p, 'tcx>>) -> Self {
|
||||||
|
Self { patterns }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.patterns.is_empty()
|
||||||
|
}
|
||||||
|
fn head_ty(&self) -> Option<Ty<'tcx>> {
|
||||||
|
if self.patterns.len() == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// If the type is opaque and it is revealed anywhere in the column, we take the revealed
|
||||||
|
// version. Otherwise we could encounter constructors for the revealed type and crash.
|
||||||
|
let is_opaque = |ty: Ty<'tcx>| matches!(ty.kind(), ty::Alias(ty::Opaque, ..));
|
||||||
|
let first_ty = self.patterns[0].ty();
|
||||||
|
if is_opaque(first_ty) {
|
||||||
|
for pat in &self.patterns {
|
||||||
|
let ty = pat.ty();
|
||||||
|
if !is_opaque(ty) {
|
||||||
|
return Some(ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(first_ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn analyze_ctors(&self, pcx: &PatCtxt<'_, 'p, 'tcx>) -> SplitConstructorSet<'tcx> {
|
||||||
|
let column_ctors = self.patterns.iter().map(|p| p.ctor());
|
||||||
|
ConstructorSet::for_ty(pcx.cx, pcx.ty).split(pcx, column_ctors)
|
||||||
|
}
|
||||||
|
fn iter<'a>(&'a self) -> impl Iterator<Item = &'p DeconstructedPat<'p, 'tcx>> + Captures<'a> {
|
||||||
|
self.patterns.iter().copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does specialization: given a constructor, this takes the patterns from the column that match
|
||||||
|
/// the constructor, and outputs their fields.
|
||||||
|
/// This returns one column per field of the constructor. The normally all have the same length
|
||||||
|
/// (the number of patterns in `self` that matched `ctor`), except that we expand or-patterns
|
||||||
|
/// which may change the lengths.
|
||||||
|
fn specialize(&self, pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: &Constructor<'tcx>) -> Vec<Self> {
|
||||||
|
let arity = ctor.arity(pcx);
|
||||||
|
if arity == 0 {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We specialize the column by `ctor`. This gives us `arity`-many columns of patterns. These
|
||||||
|
// columns may have different lengths in the presence of or-patterns (this is why we can't
|
||||||
|
// reuse `Matrix`).
|
||||||
|
let mut specialized_columns: Vec<_> =
|
||||||
|
(0..arity).map(|_| Self { patterns: Vec::new() }).collect();
|
||||||
|
let relevant_patterns =
|
||||||
|
self.patterns.iter().filter(|pat| ctor.is_covered_by(pcx, pat.ctor()));
|
||||||
|
for pat in relevant_patterns {
|
||||||
|
let specialized = pat.specialize(pcx, &ctor);
|
||||||
|
for (subpat, column) in specialized.iter().zip(&mut specialized_columns) {
|
||||||
|
if subpat.is_or_pat() {
|
||||||
|
column.patterns.extend(subpat.iter_fields())
|
||||||
|
} else {
|
||||||
|
column.patterns.push(subpat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!specialized_columns[0].is_empty(),
|
||||||
|
"ctor {ctor:?} was listed as present but isn't;
|
||||||
|
there is an inconsistency between `Constructor::is_covered_by` and `ConstructorSet::split`"
|
||||||
|
);
|
||||||
|
specialized_columns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Traverse the patterns to collect any variants of a non_exhaustive enum that fail to be mentioned
|
/// Traverse the patterns to collect any variants of a non_exhaustive enum that fail to be mentioned
|
||||||
/// in a given column. This traverses patterns column-by-column, where a column is the intuitive
|
/// in a given column.
|
||||||
/// notion of "subpatterns that inspect the same subvalue".
|
#[instrument(level = "debug", skip(cx), ret)]
|
||||||
/// Despite similarities with `is_useful`, this traversal is different. Notably this is linear in the
|
|
||||||
/// depth of patterns, whereas `is_useful` is worst-case exponential (exhaustiveness is NP-complete).
|
|
||||||
fn collect_nonexhaustive_missing_variants<'p, 'tcx>(
|
fn collect_nonexhaustive_missing_variants<'p, 'tcx>(
|
||||||
cx: &MatchCheckCtxt<'p, 'tcx>,
|
cx: &MatchCheckCtxt<'p, 'tcx>,
|
||||||
column: &[&DeconstructedPat<'p, 'tcx>],
|
column: &PatternColumn<'p, 'tcx>,
|
||||||
) -> Vec<WitnessPat<'tcx>> {
|
) -> Vec<WitnessPat<'tcx>> {
|
||||||
if column.is_empty() {
|
let Some(ty) = column.head_ty() else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
};
|
||||||
let ty = column[0].ty();
|
|
||||||
let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level: false };
|
let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level: false };
|
||||||
|
|
||||||
let set = ConstructorSet::for_ty(pcx.cx, pcx.ty).split(pcx, column.iter().map(|p| p.ctor()));
|
let set = column.analyze_ctors(pcx);
|
||||||
if set.present.is_empty() {
|
if set.present.is_empty() {
|
||||||
// We can't consistently handle the case where no constructors are present (since this would
|
// We can't consistently handle the case where no constructors are present (since this would
|
||||||
// require digging deep through any type in case there's a non_exhaustive enum somewhere),
|
// require digging deep through any type in case there's a non_exhaustive enum somewhere),
|
||||||
@ -911,35 +980,11 @@ fn collect_nonexhaustive_missing_variants<'p, 'tcx>(
|
|||||||
|
|
||||||
// Recurse into the fields.
|
// Recurse into the fields.
|
||||||
for ctor in set.present {
|
for ctor in set.present {
|
||||||
let arity = ctor.arity(pcx);
|
let specialized_columns = column.specialize(pcx, &ctor);
|
||||||
if arity == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We specialize the column by `ctor`. This gives us `arity`-many columns of patterns. These
|
|
||||||
// columns may have different lengths in the presence of or-patterns (this is why we can't
|
|
||||||
// reuse `Matrix`).
|
|
||||||
let mut specialized_columns: Vec<Vec<_>> = (0..arity).map(|_| Vec::new()).collect();
|
|
||||||
let relevant_patterns = column.iter().filter(|pat| ctor.is_covered_by(pcx, pat.ctor()));
|
|
||||||
for pat in relevant_patterns {
|
|
||||||
let specialized = pat.specialize(pcx, &ctor);
|
|
||||||
for (subpat, sub_column) in specialized.iter().zip(&mut specialized_columns) {
|
|
||||||
if subpat.is_or_pat() {
|
|
||||||
sub_column.extend(subpat.iter_fields())
|
|
||||||
} else {
|
|
||||||
sub_column.push(subpat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
debug_assert!(
|
|
||||||
!specialized_columns[0].is_empty(),
|
|
||||||
"ctor {ctor:?} was listed as present but isn't"
|
|
||||||
);
|
|
||||||
|
|
||||||
let wild_pat = WitnessPat::wild_from_ctor(pcx, ctor);
|
let wild_pat = WitnessPat::wild_from_ctor(pcx, ctor);
|
||||||
for (i, col_i) in specialized_columns.iter().enumerate() {
|
for (i, col_i) in specialized_columns.iter().enumerate() {
|
||||||
// Compute witnesses for each column.
|
// Compute witnesses for each column.
|
||||||
let wits_for_col_i = collect_nonexhaustive_missing_variants(cx, col_i.as_slice());
|
let wits_for_col_i = collect_nonexhaustive_missing_variants(cx, col_i);
|
||||||
// For each witness, we build a new pattern in the shape of `ctor(_, _, wit, _, _)`,
|
// For each witness, we build a new pattern in the shape of `ctor(_, _, wit, _, _)`,
|
||||||
// adding enough wildcards to match `arity`.
|
// adding enough wildcards to match `arity`.
|
||||||
for wit in wits_for_col_i {
|
for wit in wits_for_col_i {
|
||||||
@ -952,6 +997,81 @@ fn collect_nonexhaustive_missing_variants<'p, 'tcx>(
|
|||||||
witnesses
|
witnesses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Traverse the patterns to warn the user about ranges that overlap on their endpoints.
|
||||||
|
#[instrument(level = "debug", skip(cx, lint_root))]
|
||||||
|
fn lint_overlapping_range_endpoints<'p, 'tcx>(
|
||||||
|
cx: &MatchCheckCtxt<'p, 'tcx>,
|
||||||
|
column: &PatternColumn<'p, 'tcx>,
|
||||||
|
lint_root: HirId,
|
||||||
|
) {
|
||||||
|
let Some(ty) = column.head_ty() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level: false };
|
||||||
|
|
||||||
|
let set = column.analyze_ctors(pcx);
|
||||||
|
|
||||||
|
if IntRange::is_integral(ty) {
|
||||||
|
let emit_lint = |overlap: &IntRange, this_span: Span, overlapped_spans: &[Span]| {
|
||||||
|
let overlap_as_pat = overlap.to_pat(cx.tcx, ty);
|
||||||
|
let overlaps: Vec<_> = overlapped_spans
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.map(|span| Overlap { range: overlap_as_pat.clone(), span })
|
||||||
|
.collect();
|
||||||
|
cx.tcx.emit_spanned_lint(
|
||||||
|
lint::builtin::OVERLAPPING_RANGE_ENDPOINTS,
|
||||||
|
lint_root,
|
||||||
|
this_span,
|
||||||
|
OverlappingRangeEndpoints { overlap: overlaps, range: this_span },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// If two ranges overlapped, the split set will contain their intersection as a singleton.
|
||||||
|
let split_int_ranges = set.present.iter().filter_map(|c| c.as_int_range());
|
||||||
|
for overlap_range in split_int_ranges.clone() {
|
||||||
|
if overlap_range.is_singleton() {
|
||||||
|
let overlap: u128 = overlap_range.boundaries().0;
|
||||||
|
// Spans of ranges that start or end with the overlap.
|
||||||
|
let mut prefixes: SmallVec<[_; 1]> = Default::default();
|
||||||
|
let mut suffixes: SmallVec<[_; 1]> = Default::default();
|
||||||
|
// Iterate on patterns that contained `overlap`.
|
||||||
|
for pat in column.iter() {
|
||||||
|
let this_span = pat.span();
|
||||||
|
let Constructor::IntRange(this_range) = pat.ctor() else { continue };
|
||||||
|
if this_range.is_singleton() {
|
||||||
|
// Don't lint when one of the ranges is a singleton.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (start, end) = this_range.boundaries();
|
||||||
|
if start == overlap {
|
||||||
|
// `this_range` looks like `overlap..=end`; it overlaps with any ranges that
|
||||||
|
// look like `start..=overlap`.
|
||||||
|
if !prefixes.is_empty() {
|
||||||
|
emit_lint(overlap_range, this_span, &prefixes);
|
||||||
|
}
|
||||||
|
suffixes.push(this_span)
|
||||||
|
} else if end == overlap {
|
||||||
|
// `this_range` looks like `start..=overlap`; it overlaps with any ranges
|
||||||
|
// that look like `overlap..=end`.
|
||||||
|
if !suffixes.is_empty() {
|
||||||
|
emit_lint(overlap_range, this_span, &suffixes);
|
||||||
|
}
|
||||||
|
prefixes.push(this_span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Recurse into the fields.
|
||||||
|
for ctor in set.present {
|
||||||
|
for col in column.specialize(pcx, &ctor) {
|
||||||
|
lint_overlapping_range_endpoints(cx, &col, lint_root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The arm of a match expression.
|
/// The arm of a match expression.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub(crate) struct MatchArm<'p, 'tcx> {
|
pub(crate) struct MatchArm<'p, 'tcx> {
|
||||||
@ -1022,6 +1142,10 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>(
|
|||||||
NoWitnesses { .. } => bug!(),
|
NoWitnesses { .. } => bug!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let pat_column = arms.iter().flat_map(|arm| arm.pat.flatten_or_pat()).collect::<Vec<_>>();
|
||||||
|
let pat_column = PatternColumn::new(pat_column);
|
||||||
|
lint_overlapping_range_endpoints(cx, &pat_column, lint_root);
|
||||||
|
|
||||||
// Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hitting
|
// Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hitting
|
||||||
// `if let`s. Only run if the match is exhaustive otherwise the error is redundant.
|
// `if let`s. Only run if the match is exhaustive otherwise the error is redundant.
|
||||||
if cx.refutable
|
if cx.refutable
|
||||||
@ -1031,9 +1155,7 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>(
|
|||||||
rustc_session::lint::Level::Allow
|
rustc_session::lint::Level::Allow
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
let pat_column = arms.iter().flat_map(|arm| arm.pat.flatten_or_pat()).collect::<Vec<_>>();
|
|
||||||
let witnesses = collect_nonexhaustive_missing_variants(cx, &pat_column);
|
let witnesses = collect_nonexhaustive_missing_variants(cx, &pat_column);
|
||||||
|
|
||||||
if !witnesses.is_empty() {
|
if !witnesses.is_empty() {
|
||||||
// Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns`
|
// Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns`
|
||||||
// is not exhaustive enough.
|
// is not exhaustive enough.
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#![feature(exclusive_range_pattern)]
|
#![feature(exclusive_range_pattern)]
|
||||||
|
#![allow(overlapping_range_endpoints)]
|
||||||
|
|
||||||
// run-pass
|
// run-pass
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ macro_rules! m {
|
|||||||
$t2 => {}
|
$t2 => {}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -16,9 +16,9 @@ fn main() {
|
|||||||
m!(0u8, 30..=40, 20..=30); //~ ERROR multiple patterns overlap on their endpoints
|
m!(0u8, 30..=40, 20..=30); //~ ERROR multiple patterns overlap on their endpoints
|
||||||
m!(0u8, 20..=30, 31..=40);
|
m!(0u8, 20..=30, 31..=40);
|
||||||
m!(0u8, 20..=30, 29..=40);
|
m!(0u8, 20..=30, 29..=40);
|
||||||
m!(0u8, 20.. 30, 29..=40); //~ ERROR multiple patterns overlap on their endpoints
|
m!(0u8, 20..30, 29..=40); //~ ERROR multiple patterns overlap on their endpoints
|
||||||
m!(0u8, 20.. 30, 28..=40);
|
m!(0u8, 20..30, 28..=40);
|
||||||
m!(0u8, 20.. 30, 30..=40);
|
m!(0u8, 20..30, 30..=40);
|
||||||
m!(0u8, 20..=30, 30..=30);
|
m!(0u8, 20..=30, 30..=30);
|
||||||
m!(0u8, 20..=30, 30..=31); //~ ERROR multiple patterns overlap on their endpoints
|
m!(0u8, 20..=30, 30..=31); //~ ERROR multiple patterns overlap on their endpoints
|
||||||
m!(0u8, 20..=30, 29..=30);
|
m!(0u8, 20..=30, 29..=30);
|
||||||
@ -28,7 +28,7 @@ fn main() {
|
|||||||
m!(0u8, 20..=30, 20);
|
m!(0u8, 20..=30, 20);
|
||||||
m!(0u8, 20..=30, 25);
|
m!(0u8, 20..=30, 25);
|
||||||
m!(0u8, 20..=30, 30);
|
m!(0u8, 20..=30, 30);
|
||||||
m!(0u8, 20.. 30, 29);
|
m!(0u8, 20..30, 29);
|
||||||
m!(0u8, 20, 20..=30);
|
m!(0u8, 20, 20..=30);
|
||||||
m!(0u8, 25, 20..=30);
|
m!(0u8, 25, 20..=30);
|
||||||
m!(0u8, 30, 20..=30);
|
m!(0u8, 30, 20..=30);
|
||||||
@ -36,19 +36,21 @@ fn main() {
|
|||||||
match 0u8 {
|
match 0u8 {
|
||||||
0..=10 => {}
|
0..=10 => {}
|
||||||
20..=30 => {}
|
20..=30 => {}
|
||||||
10..=20 => {} //~ ERROR multiple patterns overlap on their endpoints
|
10..=20 => {}
|
||||||
|
//~^ ERROR multiple patterns overlap on their endpoints
|
||||||
|
//~| ERROR multiple patterns overlap on their endpoints
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
match (0u8, true) {
|
match (0u8, true) {
|
||||||
(0..=10, true) => {}
|
(0..=10, true) => {}
|
||||||
(10..20, true) => {} // not detected
|
(10..20, true) => {} //~ ERROR multiple patterns overlap on their endpoints
|
||||||
(10..20, false) => {}
|
(10..20, false) => {} //~ ERROR multiple patterns overlap on their endpoints
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
match (true, 0u8) {
|
match (true, 0u8) {
|
||||||
(true, 0..=10) => {}
|
(true, 0..=10) => {}
|
||||||
(true, 10..20) => {} //~ ERROR multiple patterns overlap on their endpoints
|
(true, 10..20) => {} //~ ERROR multiple patterns overlap on their endpoints
|
||||||
(false, 10..20) => {}
|
(false, 10..20) => {} //~ ERROR multiple patterns overlap on their endpoints
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
match Some(0u8) {
|
match Some(0u8) {
|
||||||
|
@ -24,10 +24,10 @@ LL | m!(0u8, 30..=40, 20..=30);
|
|||||||
= note: you likely meant to write mutually exclusive ranges
|
= note: you likely meant to write mutually exclusive ranges
|
||||||
|
|
||||||
error: multiple patterns overlap on their endpoints
|
error: multiple patterns overlap on their endpoints
|
||||||
--> $DIR/overlapping_range_endpoints.rs:19:22
|
--> $DIR/overlapping_range_endpoints.rs:19:21
|
||||||
|
|
|
|
||||||
LL | m!(0u8, 20.. 30, 29..=40);
|
LL | m!(0u8, 20..30, 29..=40);
|
||||||
| ------- ^^^^^^^ ... with this range
|
| ------ ^^^^^^^ ... with this range
|
||||||
| |
|
| |
|
||||||
| this range overlaps on `29_u8`...
|
| this range overlaps on `29_u8`...
|
||||||
|
|
|
|
||||||
@ -58,6 +58,15 @@ error: multiple patterns overlap on their endpoints
|
|||||||
|
|
|
|
||||||
LL | 0..=10 => {}
|
LL | 0..=10 => {}
|
||||||
| ------ this range overlaps on `10_u8`...
|
| ------ this range overlaps on `10_u8`...
|
||||||
|
LL | 20..=30 => {}
|
||||||
|
LL | 10..=20 => {}
|
||||||
|
| ^^^^^^^ ... with this range
|
||||||
|
|
|
||||||
|
= note: you likely meant to write mutually exclusive ranges
|
||||||
|
|
||||||
|
error: multiple patterns overlap on their endpoints
|
||||||
|
--> $DIR/overlapping_range_endpoints.rs:39:9
|
||||||
|
|
|
||||||
LL | 20..=30 => {}
|
LL | 20..=30 => {}
|
||||||
| ------- this range overlaps on `20_u8`...
|
| ------- this range overlaps on `20_u8`...
|
||||||
LL | 10..=20 => {}
|
LL | 10..=20 => {}
|
||||||
@ -66,7 +75,28 @@ LL | 10..=20 => {}
|
|||||||
= note: you likely meant to write mutually exclusive ranges
|
= note: you likely meant to write mutually exclusive ranges
|
||||||
|
|
||||||
error: multiple patterns overlap on their endpoints
|
error: multiple patterns overlap on their endpoints
|
||||||
--> $DIR/overlapping_range_endpoints.rs:50:16
|
--> $DIR/overlapping_range_endpoints.rs:46:10
|
||||||
|
|
|
||||||
|
LL | (0..=10, true) => {}
|
||||||
|
| ------ this range overlaps on `10_u8`...
|
||||||
|
LL | (10..20, true) => {}
|
||||||
|
| ^^^^^^ ... with this range
|
||||||
|
|
|
||||||
|
= note: you likely meant to write mutually exclusive ranges
|
||||||
|
|
||||||
|
error: multiple patterns overlap on their endpoints
|
||||||
|
--> $DIR/overlapping_range_endpoints.rs:47:10
|
||||||
|
|
|
||||||
|
LL | (0..=10, true) => {}
|
||||||
|
| ------ this range overlaps on `10_u8`...
|
||||||
|
LL | (10..20, true) => {}
|
||||||
|
LL | (10..20, false) => {}
|
||||||
|
| ^^^^^^ ... with this range
|
||||||
|
|
|
||||||
|
= note: you likely meant to write mutually exclusive ranges
|
||||||
|
|
||||||
|
error: multiple patterns overlap on their endpoints
|
||||||
|
--> $DIR/overlapping_range_endpoints.rs:52:16
|
||||||
|
|
|
|
||||||
LL | (true, 0..=10) => {}
|
LL | (true, 0..=10) => {}
|
||||||
| ------ this range overlaps on `10_u8`...
|
| ------ this range overlaps on `10_u8`...
|
||||||
@ -76,7 +106,18 @@ LL | (true, 10..20) => {}
|
|||||||
= note: you likely meant to write mutually exclusive ranges
|
= note: you likely meant to write mutually exclusive ranges
|
||||||
|
|
||||||
error: multiple patterns overlap on their endpoints
|
error: multiple patterns overlap on their endpoints
|
||||||
--> $DIR/overlapping_range_endpoints.rs:56:14
|
--> $DIR/overlapping_range_endpoints.rs:53:17
|
||||||
|
|
|
||||||
|
LL | (true, 0..=10) => {}
|
||||||
|
| ------ this range overlaps on `10_u8`...
|
||||||
|
LL | (true, 10..20) => {}
|
||||||
|
LL | (false, 10..20) => {}
|
||||||
|
| ^^^^^^ ... with this range
|
||||||
|
|
|
||||||
|
= note: you likely meant to write mutually exclusive ranges
|
||||||
|
|
||||||
|
error: multiple patterns overlap on their endpoints
|
||||||
|
--> $DIR/overlapping_range_endpoints.rs:58:14
|
||||||
|
|
|
|
||||||
LL | Some(0..=10) => {}
|
LL | Some(0..=10) => {}
|
||||||
| ------ this range overlaps on `10_u8`...
|
| ------ this range overlaps on `10_u8`...
|
||||||
@ -85,5 +126,5 @@ LL | Some(10..20) => {}
|
|||||||
|
|
|
|
||||||
= note: you likely meant to write mutually exclusive ranges
|
= note: you likely meant to write mutually exclusive ranges
|
||||||
|
|
||||||
error: aborting due to 8 previous errors
|
error: aborting due to 12 previous errors
|
||||||
|
|
||||||
|
@ -26,11 +26,9 @@ fn upvar() {
|
|||||||
fn enum_upvar() {
|
fn enum_upvar() {
|
||||||
type T = impl Copy;
|
type T = impl Copy;
|
||||||
let foo: T = Some((1u32, 2u32));
|
let foo: T = Some((1u32, 2u32));
|
||||||
let x = move || {
|
let x = move || match foo {
|
||||||
match foo {
|
None => (),
|
||||||
None => (),
|
Some((a, b)) => (),
|
||||||
Some((a, b)) => (),
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +61,19 @@ mod only_pattern {
|
|||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type V = impl Copy;
|
||||||
|
|
||||||
|
fn baz(baz: Option<V>) {
|
||||||
|
match baz {
|
||||||
|
_ => {}
|
||||||
|
Some((mut x, mut y)) => {
|
||||||
|
x = 42;
|
||||||
|
y = "foo";
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod only_pattern_rpit {
|
mod only_pattern_rpit {
|
||||||
@ -71,11 +82,7 @@ mod only_pattern_rpit {
|
|||||||
let (mut x, mut y) = foo(false);
|
let (mut x, mut y) = foo(false);
|
||||||
x = 42;
|
x = 42;
|
||||||
y = "foo";
|
y = "foo";
|
||||||
if b {
|
if b { panic!() } else { foo(true) }
|
||||||
panic!()
|
|
||||||
} else {
|
|
||||||
foo(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bar(b: bool) -> Option<impl Copy> {
|
fn bar(b: bool) -> Option<impl Copy> {
|
||||||
|
Loading…
Reference in New Issue
Block a user