Skip to content

Commit

Permalink
Auto merge of rust-lang#116751 - Nadrieril:lint-overlap-per-column, r…
Browse files Browse the repository at this point in the history
…=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.
  • Loading branch information
bors committed Oct 27, 2023
2 parents 6888929 + 3fa2e71 commit 9d6d5d4
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 148 deletions.
76 changes: 6 additions & 70 deletions compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,20 @@ use smallvec::{smallvec, SmallVec};
use rustc_apfloat::ieee::{DoubleS, IeeeFloat, SingleS};
use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::{HirId, RangeEnd};
use rustc_hir::RangeEnd;
use rustc_index::Idx;
use rustc_middle::middle::stability::EvalResult;
use rustc_middle::mir;
use rustc_middle::thir::{FieldPat, Pat, PatKind, PatRange};
use rustc_middle::ty::layout::IntegerExt;
use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef};
use rustc_session::lint;
use rustc_span::{Span, DUMMY_SP};
use rustc_target::abi::{FieldIdx, Integer, VariantIdx, FIRST_VARIANT};

use self::Constructor::*;
use self::SliceKind::*;

use super::usefulness::{MatchCheckCtxt, PatCtxt};
use crate::errors::{Overlap, OverlappingRangeEndpoints};

/// 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>> {
Expand Down Expand Up @@ -111,15 +109,15 @@ pub(crate) struct IntRange {

impl IntRange {
#[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)
}

fn is_singleton(&self) -> bool {
pub(super) fn is_singleton(&self) -> bool {
self.range.start() == self.range.end()
}

fn boundaries(&self) -> (u128, u128) {
pub(super) fn boundaries(&self) -> (u128, u128) {
(*self.range.start(), *self.range.end())
}

Expand Down Expand Up @@ -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
/// integer ranges as explained at the top of the file.
///
Expand Down Expand Up @@ -293,7 +274,7 @@ impl IntRange {
}

/// 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 bias = IntRange::signed_bias(tcx, ty);
Expand All @@ -315,51 +296,6 @@ impl IntRange {

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
Expand Down Expand Up @@ -644,7 +580,7 @@ impl<'tcx> Constructor<'tcx> {
_ => None,
}
}
fn as_int_range(&self) -> Option<&IntRange> {
pub(super) fn as_int_range(&self) -> Option<&IntRange> {
match self {
IntRange(range) => Some(range),
_ => None,
Expand Down
Loading

0 comments on commit 9d6d5d4

Please sign in to comment.