about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-03-09 03:49:01 +0000
committerbors <bors@rust-lang.org>2024-03-09 03:49:01 +0000
commit1b427b3bf79c2cd48c75915301be3b009b82dea3 (patch)
tree3567f55ab86ee59c0a67343c4d1749c01d5b0226
parent4d4bb491b65c300835442f6cb4f34fc9a5685c26 (diff)
parent8ac9a04257f73d9861625816d4c741096dd69c67 (diff)
downloadrust-1b427b3bf79c2cd48c75915301be3b009b82dea3.tar.gz
rust-1b427b3bf79c2cd48c75915301be3b009b82dea3.zip
Auto merge of #118879 - Nadrieril:lint-range-gap, r=estebank
Lint singleton gaps after exclusive ranges

In the discussion to stabilize exclusive range patterns (https://github.com/rust-lang/rust/issues/37854), it has often come up that they're likely to cause off-by-one mistakes. We already have the `overlapping_range_endpoints` lint, so I [proposed](https://github.com/rust-lang/rust/issues/37854#issuecomment-1845580712) a lint to catch the complementary mistake.

This PR adds a new `non_contiguous_range_endpoints` lint that catches likely off-by-one errors with exclusive range patterns. Here's the idea (see the test file for more examples):
```rust
match x {
    0..10 => ..., // WARN: this range doesn't match `10_u8` because `..` is an exclusive range
    11..20 => ..., // this could appear to continue range `0_u8..10_u8`, but `10_u8` isn't matched by either of them
    _ => ...,
}
// help: use an inclusive range instead: `0_u8..=10_u8`
```

More precisely: for any exclusive range `lo..hi`, if `hi+1` is matched by another range but `hi` isn't, we suggest writing an inclusive range `lo..=hi` instead. We also catch `lo..T::MAX`.
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs31
-rw-r--r--compiler/rustc_pattern_analysis/messages.ftl8
-rw-r--r--compiler/rustc_pattern_analysis/src/constructor.rs35
-rw-r--r--compiler/rustc_pattern_analysis/src/errors.rs51
-rw-r--r--compiler/rustc_pattern_analysis/src/lib.rs23
-rw-r--r--compiler/rustc_pattern_analysis/src/rustc.rs72
-rw-r--r--compiler/rustc_pattern_analysis/src/usefulness.rs48
-rw-r--r--src/tools/clippy/tests/ui/manual_range_patterns.fixed1
-rw-r--r--src/tools/clippy/tests/ui/manual_range_patterns.rs1
-rw-r--r--src/tools/clippy/tests/ui/manual_range_patterns.stderr38
-rw-r--r--tests/ui/half-open-range-patterns/half-open-range-pats-exhaustive-fail.rs1
-rw-r--r--tests/ui/half-open-range-patterns/half-open-range-pats-exhaustive-fail.stderr136
-rw-r--r--tests/ui/half-open-range-patterns/range_pat_interactions0.rs1
-rw-r--r--tests/ui/match/issue-18060.rs1
-rw-r--r--tests/ui/mir/mir_match_test.rs1
-rw-r--r--tests/ui/pattern/usefulness/integer-ranges/exhaustiveness.rs1
-rw-r--r--tests/ui/pattern/usefulness/integer-ranges/exhaustiveness.stderr24
-rw-r--r--tests/ui/pattern/usefulness/integer-ranges/gap_between_ranges.rs117
-rw-r--r--tests/ui/pattern/usefulness/integer-ranges/gap_between_ranges.stderr193
-rw-r--r--tests/ui/pattern/usefulness/integer-ranges/reachability.rs1
-rw-r--r--tests/ui/pattern/usefulness/integer-ranges/reachability.stderr52
21 files changed, 679 insertions, 157 deletions
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index 94f8bbe2437..f2560b35aa2 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -67,6 +67,7 @@ declare_lint_pass! {
         MISSING_FRAGMENT_SPECIFIER,
         MUST_NOT_SUSPEND,
         NAMED_ARGUMENTS_USED_POSITIONALLY,
+        NON_CONTIGUOUS_RANGE_ENDPOINTS,
         NON_EXHAUSTIVE_OMITTED_PATTERNS,
         ORDER_DEPENDENT_TRAIT_OBJECTS,
         OVERLAPPING_RANGE_ENDPOINTS,
@@ -813,6 +814,36 @@ declare_lint! {
 }
 
 declare_lint! {
+    /// The `non_contiguous_range_endpoints` lint detects likely off-by-one errors when using
+    /// exclusive [range patterns].
+    ///
+    /// [range patterns]: https://doc.rust-lang.org/nightly/reference/patterns.html#range-patterns
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// # #![feature(exclusive_range_pattern)]
+    /// let x = 123u32;
+    /// match x {
+    ///     0..100 => { println!("small"); }
+    ///     101..1000 => { println!("large"); }
+    ///     _ => { println!("larger"); }
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// It is likely a mistake to have range patterns in a match expression that miss out a single
+    /// number. Check that the beginning and end values are what you expect, and keep in mind that
+    /// with `..=` the right bound is inclusive, and with `..` it is exclusive.
+    pub NON_CONTIGUOUS_RANGE_ENDPOINTS,
+    Warn,
+    "detects off-by-one errors with exclusive range patterns"
+}
+
+declare_lint! {
     /// The `bindings_with_variant_name` lint detects pattern bindings with
     /// the same name as one of the matched variants.
     ///
diff --git a/compiler/rustc_pattern_analysis/messages.ftl b/compiler/rustc_pattern_analysis/messages.ftl
index 827928f97d7..41a1d958f10 100644
--- a/compiler/rustc_pattern_analysis/messages.ftl
+++ b/compiler/rustc_pattern_analysis/messages.ftl
@@ -1,3 +1,11 @@
+pattern_analysis_excluside_range_missing_gap = multiple ranges are one apart
+    .label = this range doesn't match `{$gap}` because `..` is an exclusive range
+    .suggestion = use an inclusive range instead
+
+pattern_analysis_excluside_range_missing_max = exclusive range missing `{$max}`
+    .label = this range doesn't match `{$max}` because `..` is an exclusive range
+    .suggestion = use an inclusive range instead
+
 pattern_analysis_non_exhaustive_omitted_pattern = some variants are not matched explicitly
     .help = ensure that all variants are matched explicitly by adding the suggested match arms
     .note = the matched value is of type `{$scrut_ty}` and the `non_exhaustive_omitted_patterns` attribute was found
diff --git a/compiler/rustc_pattern_analysis/src/constructor.rs b/compiler/rustc_pattern_analysis/src/constructor.rs
index 767227619b0..69e294e47a5 100644
--- a/compiler/rustc_pattern_analysis/src/constructor.rs
+++ b/compiler/rustc_pattern_analysis/src/constructor.rs
@@ -192,7 +192,7 @@ impl fmt::Display for RangeEnd {
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
 pub enum MaybeInfiniteInt {
     NegInfinity,
-    /// Encoded value. DO NOT CONSTRUCT BY HAND; use `new_finite`.
+    /// Encoded value. DO NOT CONSTRUCT BY HAND; use `new_finite_{int,uint}`.
     #[non_exhaustive]
     Finite(u128),
     /// The integer after `u128::MAX`. We need it to represent `x..=u128::MAX` as an exclusive range.
@@ -229,25 +229,22 @@ impl MaybeInfiniteInt {
     }
 
     /// Note: this will not turn a finite value into an infinite one or vice-versa.
-    pub fn minus_one(self) -> Self {
+    pub fn minus_one(self) -> Option<Self> {
         match self {
-            Finite(n) => match n.checked_sub(1) {
-                Some(m) => Finite(m),
-                None => panic!("Called `MaybeInfiniteInt::minus_one` on 0"),
-            },
-            JustAfterMax => Finite(u128::MAX),
-            x => x,
+            Finite(n) => n.checked_sub(1).map(Finite),
+            JustAfterMax => Some(Finite(u128::MAX)),
+            x => Some(x),
         }
     }
     /// Note: this will not turn a finite value into an infinite one or vice-versa.
-    pub fn plus_one(self) -> Self {
+    pub fn plus_one(self) -> Option<Self> {
         match self {
             Finite(n) => match n.checked_add(1) {
-                Some(m) => Finite(m),
-                None => JustAfterMax,
+                Some(m) => Some(Finite(m)),
+                None => Some(JustAfterMax),
             },
-            JustAfterMax => panic!("Called `MaybeInfiniteInt::plus_one` on u128::MAX+1"),
-            x => x,
+            JustAfterMax => None,
+            x => Some(x),
         }
     }
 }
@@ -268,18 +265,24 @@ impl IntRange {
     pub fn is_singleton(&self) -> bool {
         // Since `lo` and `hi` can't be the same `Infinity` and `plus_one` never changes from finite
         // to infinite, this correctly only detects ranges that contain exacly one `Finite(x)`.
-        self.lo.plus_one() == self.hi
+        self.lo.plus_one() == Some(self.hi)
     }
 
+    /// Construct a singleton range.
+    /// `x` must be a `Finite(_)` value.
     #[inline]
     pub fn from_singleton(x: MaybeInfiniteInt) -> IntRange {
-        IntRange { lo: x, hi: x.plus_one() }
+        // `unwrap()` is ok on a finite value
+        IntRange { lo: x, hi: x.plus_one().unwrap() }
     }
 
+    /// Construct a range with these boundaries.
+    /// `lo` must not be `PosInfinity` or `JustAfterMax`. `hi` must not be `NegInfinity`.
+    /// If `end` is `Included`, `hi` must also not be `JustAfterMax`.
     #[inline]
     pub fn from_range(lo: MaybeInfiniteInt, mut hi: MaybeInfiniteInt, end: RangeEnd) -> IntRange {
         if end == RangeEnd::Included {
-            hi = hi.plus_one();
+            hi = hi.plus_one().unwrap();
         }
         if lo >= hi {
             // This should have been caught earlier by E0030.
diff --git a/compiler/rustc_pattern_analysis/src/errors.rs b/compiler/rustc_pattern_analysis/src/errors.rs
index d0f56f0268d..e471b8abd73 100644
--- a/compiler/rustc_pattern_analysis/src/errors.rs
+++ b/compiler/rustc_pattern_analysis/src/errors.rs
@@ -77,6 +77,57 @@ impl<'tcx> AddToDiagnostic for Overlap<'tcx> {
 }
 
 #[derive(LintDiagnostic)]
+#[diag(pattern_analysis_excluside_range_missing_max)]
+pub struct ExclusiveRangeMissingMax<'tcx> {
+    #[label]
+    #[suggestion(code = "{suggestion}", applicability = "maybe-incorrect")]
+    /// This is an exclusive range that looks like `lo..max` (i.e. doesn't match `max`).
+    pub first_range: Span,
+    /// Suggest `lo..=max` instead.
+    pub suggestion: String,
+    pub max: Pat<'tcx>,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(pattern_analysis_excluside_range_missing_gap)]
+pub struct ExclusiveRangeMissingGap<'tcx> {
+    #[label]
+    #[suggestion(code = "{suggestion}", applicability = "maybe-incorrect")]
+    /// This is an exclusive range that looks like `lo..gap` (i.e. doesn't match `gap`).
+    pub first_range: Span,
+    pub gap: Pat<'tcx>,
+    /// Suggest `lo..=gap` instead.
+    pub suggestion: String,
+    #[subdiagnostic]
+    /// All these ranges skipped over `gap` which we think is probably a mistake.
+    pub gap_with: Vec<GappedRange<'tcx>>,
+}
+
+pub struct GappedRange<'tcx> {
+    pub span: Span,
+    pub gap: Pat<'tcx>,
+    pub first_range: Pat<'tcx>,
+}
+
+impl<'tcx> AddToDiagnostic for GappedRange<'tcx> {
+    fn add_to_diagnostic_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
+        self,
+        diag: &mut Diag<'_, G>,
+        _: F,
+    ) {
+        let GappedRange { span, gap, first_range } = self;
+
+        // FIXME(mejrs) unfortunately `#[derive(LintDiagnostic)]`
+        // does not support `#[subdiagnostic(eager)]`...
+        let message = format!(
+            "this could appear to continue range `{first_range}`, but `{gap}` isn't matched by \
+            either of them"
+        );
+        diag.span_label(span, message);
+    }
+}
+
+#[derive(LintDiagnostic)]
 #[diag(pattern_analysis_non_exhaustive_omitted_pattern)]
 #[help]
 #[note]
diff --git a/compiler/rustc_pattern_analysis/src/lib.rs b/compiler/rustc_pattern_analysis/src/lib.rs
index 4b0955699fc..f632eaf7ea4 100644
--- a/compiler/rustc_pattern_analysis/src/lib.rs
+++ b/compiler/rustc_pattern_analysis/src/lib.rs
@@ -70,14 +70,8 @@ use rustc_middle::ty::Ty;
 use rustc_span::ErrorGuaranteed;
 
 use crate::constructor::{Constructor, ConstructorSet, IntRange};
-#[cfg(feature = "rustc")]
-use crate::lints::lint_nonexhaustive_missing_variants;
 use crate::pat::DeconstructedPat;
 use crate::pat_column::PatternColumn;
-#[cfg(feature = "rustc")]
-use crate::rustc::RustcMatchCheckCtxt;
-#[cfg(feature = "rustc")]
-use crate::usefulness::{compute_match_usefulness, ValidityConstraint};
 
 pub trait Captures<'a> {}
 impl<'a, T: ?Sized> Captures<'a> for T {}
@@ -145,6 +139,18 @@ pub trait TypeCx: Sized + fmt::Debug {
 
     /// The maximum pattern complexity limit was reached.
     fn complexity_exceeded(&self) -> Result<(), Self::Error>;
+
+    /// Lint that there is a gap `gap` between `pat` and all of `gapped_with` such that the gap is
+    /// not matched by another range. If `gapped_with` is empty, then `gap` is `T::MAX`. We only
+    /// detect singleton gaps.
+    /// The default implementation does nothing.
+    fn lint_non_contiguous_range_endpoints(
+        &self,
+        _pat: &DeconstructedPat<Self>,
+        _gap: IntRange,
+        _gapped_with: &[&DeconstructedPat<Self>],
+    ) {
+    }
 }
 
 /// The arm of a match expression.
@@ -167,11 +173,14 @@ impl<'p, Cx: TypeCx> Copy for MatchArm<'p, Cx> {}
 /// useful, and runs some lints.
 #[cfg(feature = "rustc")]
 pub fn analyze_match<'p, 'tcx>(
-    tycx: &RustcMatchCheckCtxt<'p, 'tcx>,
+    tycx: &rustc::RustcMatchCheckCtxt<'p, 'tcx>,
     arms: &[rustc::MatchArm<'p, 'tcx>],
     scrut_ty: Ty<'tcx>,
     pattern_complexity_limit: Option<usize>,
 ) -> Result<rustc::UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> {
+    use lints::lint_nonexhaustive_missing_variants;
+    use usefulness::{compute_match_usefulness, ValidityConstraint};
+
     let scrut_ty = tycx.reveal_opaque_ty(scrut_ty);
     let scrut_validity = ValidityConstraint::from_bool(tycx.known_valid_scrutinee);
     let report =
diff --git a/compiler/rustc_pattern_analysis/src/rustc.rs b/compiler/rustc_pattern_analysis/src/rustc.rs
index 5f5bfa7154a..0085f0ab656 100644
--- a/compiler/rustc_pattern_analysis/src/rustc.rs
+++ b/compiler/rustc_pattern_analysis/src/rustc.rs
@@ -8,7 +8,7 @@ use rustc_index::{Idx, IndexVec};
 use rustc_middle::middle::stability::EvalResult;
 use rustc_middle::mir::interpret::Scalar;
 use rustc_middle::mir::{self, Const};
-use rustc_middle::thir::{FieldPat, Pat, PatKind, PatRange, PatRangeBoundary};
+use rustc_middle::thir::{self, FieldPat, Pat, PatKind, PatRange, PatRangeBoundary};
 use rustc_middle::ty::layout::IntegerExt;
 use rustc_middle::ty::{self, FieldDef, OpaqueTypeKey, Ty, TyCtxt, TypeVisitableExt, VariantDef};
 use rustc_session::lint;
@@ -718,12 +718,12 @@ impl<'p, 'tcx: 'p> RustcMatchCheckCtxt<'p, 'tcx> {
                 let value = mir::Const::from_ty_const(c, cx.tcx);
                 lo = PatRangeBoundary::Finite(value);
             }
-            let hi = if matches!(range.hi, Finite(0)) {
+            let hi = if let Some(hi) = range.hi.minus_one() {
+                hi
+            } else {
                 // The range encodes `..ty::MIN`, so we can't convert it to an inclusive range.
                 end = rustc_hir::RangeEnd::Excluded;
                 range.hi
-            } else {
-                range.hi.minus_one()
             };
             let hi = cx.hoist_pat_range_bdy(hi, ty);
             PatKind::Range(Box::new(PatRange { lo, hi, end, ty: ty.inner() }))
@@ -900,6 +900,70 @@ impl<'p, 'tcx: 'p> TypeCx for RustcMatchCheckCtxt<'p, 'tcx> {
         let span = self.whole_match_span.unwrap_or(self.scrut_span);
         Err(self.tcx.dcx().span_err(span, "reached pattern complexity limit"))
     }
+
+    fn lint_non_contiguous_range_endpoints(
+        &self,
+        pat: &crate::pat::DeconstructedPat<Self>,
+        gap: IntRange,
+        gapped_with: &[&crate::pat::DeconstructedPat<Self>],
+    ) {
+        let Some(&thir_pat) = pat.data() else { return };
+        let thir::PatKind::Range(range) = &thir_pat.kind else { return };
+        // Only lint when the left range is an exclusive range.
+        if range.end != rustc_hir::RangeEnd::Excluded {
+            return;
+        }
+        // `pat` is an exclusive range like `lo..gap`. `gapped_with` contains ranges that start with
+        // `gap+1`.
+        let suggested_range: thir::Pat<'_> = {
+            // Suggest `lo..=gap` instead.
+            let mut suggested_range = thir_pat.clone();
+            let thir::PatKind::Range(range) = &mut suggested_range.kind else { unreachable!() };
+            range.end = rustc_hir::RangeEnd::Included;
+            suggested_range
+        };
+        let gap_as_pat = self.hoist_pat_range(&gap, *pat.ty());
+        if gapped_with.is_empty() {
+            // If `gapped_with` is empty, `gap == T::MAX`.
+            self.tcx.emit_node_span_lint(
+                lint::builtin::NON_CONTIGUOUS_RANGE_ENDPOINTS,
+                self.match_lint_level,
+                thir_pat.span,
+                errors::ExclusiveRangeMissingMax {
+                    // Point at this range.
+                    first_range: thir_pat.span,
+                    // That's the gap that isn't covered.
+                    max: gap_as_pat.clone(),
+                    // Suggest `lo..=max` instead.
+                    suggestion: suggested_range.to_string(),
+                },
+            );
+        } else {
+            self.tcx.emit_node_span_lint(
+                lint::builtin::NON_CONTIGUOUS_RANGE_ENDPOINTS,
+                self.match_lint_level,
+                thir_pat.span,
+                errors::ExclusiveRangeMissingGap {
+                    // Point at this range.
+                    first_range: thir_pat.span,
+                    // That's the gap that isn't covered.
+                    gap: gap_as_pat.clone(),
+                    // Suggest `lo..=gap` instead.
+                    suggestion: suggested_range.to_string(),
+                    // All these ranges skipped over `gap` which we think is probably a
+                    // mistake.
+                    gap_with: gapped_with
+                        .iter()
+                        .map(|pat| errors::GappedRange {
+                            span: pat.data().unwrap().span,
+                            gap: gap_as_pat.clone(),
+                            first_range: thir_pat.clone(),
+                        })
+                        .collect(),
+                },
+            );
+        }
+    }
 }
 
 /// Recursively expand this pattern into its subpatterns. Only useful for or-patterns.
diff --git a/compiler/rustc_pattern_analysis/src/usefulness.rs b/compiler/rustc_pattern_analysis/src/usefulness.rs
index c518844cc5e..a067bf1f0c2 100644
--- a/compiler/rustc_pattern_analysis/src/usefulness.rs
+++ b/compiler/rustc_pattern_analysis/src/usefulness.rs
@@ -1489,7 +1489,7 @@ impl<Cx: TypeCx> WitnessMatrix<Cx> {
 /// We can however get false negatives because exhaustiveness does not explore all cases. See the
 /// section on relevancy at the top of the file.
 fn collect_overlapping_range_endpoints<'p, Cx: TypeCx>(
-    mcx: &mut UsefulnessCtxt<'_, Cx>,
+    cx: &Cx,
     overlap_range: IntRange,
     matrix: &Matrix<'p, Cx>,
     specialized_matrix: &Matrix<'p, Cx>,
@@ -1522,11 +1522,11 @@ fn collect_overlapping_range_endpoints<'p, Cx: TypeCx>(
                     .map(|&(_, pat)| pat)
                     .collect();
                 if !overlaps_with.is_empty() {
-                    mcx.tycx.lint_overlapping_range_endpoints(pat, overlap_range, &overlaps_with);
+                    cx.lint_overlapping_range_endpoints(pat, overlap_range, &overlaps_with);
                 }
             }
             suffixes.push((child_row_id, pat))
-        } else if this_range.hi == overlap.plus_one() {
+        } else if Some(this_range.hi) == overlap.plus_one() {
             // `this_range` looks like `this_range.lo..=overlap`; it overlaps with any
             // ranges that look like `overlap..=hi`.
             if !suffixes.is_empty() {
@@ -1538,7 +1538,7 @@ fn collect_overlapping_range_endpoints<'p, Cx: TypeCx>(
                     .map(|&(_, pat)| pat)
                     .collect();
                 if !overlaps_with.is_empty() {
-                    mcx.tycx.lint_overlapping_range_endpoints(pat, overlap_range, &overlaps_with);
+                    cx.lint_overlapping_range_endpoints(pat, overlap_range, &overlaps_with);
                 }
             }
             prefixes.push((child_row_id, pat))
@@ -1546,6 +1546,33 @@ fn collect_overlapping_range_endpoints<'p, Cx: TypeCx>(
     }
 }
 
+/// Collect ranges that have a singleton gap between them.
+fn collect_non_contiguous_range_endpoints<'p, Cx: TypeCx>(
+    cx: &Cx,
+    gap_range: &IntRange,
+    matrix: &Matrix<'p, Cx>,
+) {
+    let gap = gap_range.lo;
+    // Ranges that look like `lo..gap`.
+    let mut onebefore: SmallVec<[_; 1]> = Default::default();
+    // Ranges that start on `gap+1` or singletons `gap+1`.
+    let mut oneafter: SmallVec<[_; 1]> = Default::default();
+    // Look through the column for ranges near the gap.
+    for pat in matrix.heads() {
+        let PatOrWild::Pat(pat) = pat else { continue };
+        let Constructor::IntRange(this_range) = pat.ctor() else { continue };
+        if gap == this_range.hi {
+            onebefore.push(pat)
+        } else if gap.plus_one() == Some(this_range.lo) {
+            oneafter.push(pat)
+        }
+    }
+
+    for pat_before in onebefore {
+        cx.lint_non_contiguous_range_endpoints(pat_before, *gap_range, oneafter.as_slice());
+    }
+}
+
 /// The core of the algorithm.
 ///
 /// This recursively computes witnesses of the non-exhaustiveness of `matrix` (if any). Also tracks
@@ -1626,13 +1653,24 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
                 && spec_matrix.rows.len() >= 2
                 && spec_matrix.rows.iter().any(|row| !row.intersects.is_empty())
             {
-                collect_overlapping_range_endpoints(mcx, overlap_range, matrix, &spec_matrix);
+                collect_overlapping_range_endpoints(mcx.tycx, overlap_range, matrix, &spec_matrix);
             }
         }
 
         matrix.unspecialize(spec_matrix);
     }
 
+    // Detect singleton gaps between ranges.
+    if missing_ctors.iter().any(|c| matches!(c, Constructor::IntRange(..))) {
+        for missing in &missing_ctors {
+            if let Constructor::IntRange(gap) = missing {
+                if gap.is_singleton() {
+                    collect_non_contiguous_range_endpoints(mcx.tycx, gap, matrix);
+                }
+            }
+        }
+    }
+
     // Record usefulness in the patterns.
     for row in matrix.rows() {
         if row.useful {
diff --git a/src/tools/clippy/tests/ui/manual_range_patterns.fixed b/src/tools/clippy/tests/ui/manual_range_patterns.fixed
index b348d7071f6..e9f6fbcc3fc 100644
--- a/src/tools/clippy/tests/ui/manual_range_patterns.fixed
+++ b/src/tools/clippy/tests/ui/manual_range_patterns.fixed
@@ -1,4 +1,5 @@
 #![allow(unused)]
+#![allow(non_contiguous_range_endpoints)]
 #![warn(clippy::manual_range_patterns)]
 #![feature(exclusive_range_pattern)]
 
diff --git a/src/tools/clippy/tests/ui/manual_range_patterns.rs b/src/tools/clippy/tests/ui/manual_range_patterns.rs
index a0750f54b73..d525aaa24ad 100644
--- a/src/tools/clippy/tests/ui/manual_range_patterns.rs
+++ b/src/tools/clippy/tests/ui/manual_range_patterns.rs
@@ -1,4 +1,5 @@
 #![allow(unused)]
+#![allow(non_contiguous_range_endpoints)]
 #![warn(clippy::manual_range_patterns)]
 #![feature(exclusive_range_pattern)]
 
diff --git a/src/tools/clippy/tests/ui/manual_range_patterns.stderr b/src/tools/clippy/tests/ui/manual_range_patterns.stderr
index 7c19fdd475f..af9256aeea3 100644
--- a/src/tools/clippy/tests/ui/manual_range_patterns.stderr
+++ b/src/tools/clippy/tests/ui/manual_range_patterns.stderr
@@ -1,5 +1,5 @@
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:8:25
+  --> tests/ui/manual_range_patterns.rs:9:25
    |
 LL |     let _ = matches!(f, 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10);
    |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10`
@@ -8,109 +8,109 @@ LL |     let _ = matches!(f, 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10);
    = help: to override `-D warnings` add `#[allow(clippy::manual_range_patterns)]`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:9:25
+  --> tests/ui/manual_range_patterns.rs:10:25
    |
 LL |     let _ = matches!(f, 4 | 2 | 3 | 1 | 5 | 6 | 9 | 7 | 8 | 10);
    |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:16:25
+  --> tests/ui/manual_range_patterns.rs:17:25
    |
 LL |     let _ = matches!(f, 1 | (2..=4));
    |                         ^^^^^^^^^^^ help: try: `1..=4`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:17:25
+  --> tests/ui/manual_range_patterns.rs:18:25
    |
 LL |     let _ = matches!(f, 1 | (2..4));
    |                         ^^^^^^^^^^ help: try: `1..4`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:18:25
+  --> tests/ui/manual_range_patterns.rs:19:25
    |
 LL |     let _ = matches!(f, (1..=10) | (2..=13) | (14..=48324728) | 48324729);
    |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=48324729`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:19:25
+  --> tests/ui/manual_range_patterns.rs:20:25
    |
 LL |     let _ = matches!(f, 0 | (1..=10) | 48324730 | (2..=13) | (14..=48324728) | 48324729);
    |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `0..=48324730`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:20:25
+  --> tests/ui/manual_range_patterns.rs:21:25
    |
 LL |     let _ = matches!(f, 0..=1 | 0..=2 | 0..=3);
    |                         ^^^^^^^^^^^^^^^^^^^^^ help: try: `0..=3`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:23:9
+  --> tests/ui/manual_range_patterns.rs:24:9
    |
 LL |         1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 => true,
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:26:25
+  --> tests/ui/manual_range_patterns.rs:27:25
    |
 LL |     let _ = matches!(f, -1 | -5 | 3 | -2 | -4 | -3 | 0 | 1 | 2);
    |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-5..=3`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:28:25
+  --> tests/ui/manual_range_patterns.rs:29:25
    |
 LL |     let _ = matches!(f, -1_000_000..=1_000_000 | -1_000_001 | 1_000_001);
    |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-1_000_001..=1_000_001`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:31:17
+  --> tests/ui/manual_range_patterns.rs:32:17
    |
 LL |     matches!(f, 0x00 | 0x01 | 0x02 | 0x03);
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `0x00..=0x03`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:32:17
+  --> tests/ui/manual_range_patterns.rs:33:17
    |
 LL |     matches!(f, 0x00..=0x05 | 0x06 | 0x07);
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `0x00..=0x07`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:33:17
+  --> tests/ui/manual_range_patterns.rs:34:17
    |
 LL |     matches!(f, -0x09 | -0x08 | -0x07..=0x00);
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-0x09..=0x00`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:35:17
+  --> tests/ui/manual_range_patterns.rs:36:17
    |
 LL |     matches!(f, 0..5 | 5);
    |                 ^^^^^^^^ help: try: `0..=5`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:36:17
+  --> tests/ui/manual_range_patterns.rs:37:17
    |
 LL |     matches!(f, 0 | 1..5);
    |                 ^^^^^^^^ help: try: `0..5`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:38:17
+  --> tests/ui/manual_range_patterns.rs:39:17
    |
 LL |     matches!(f, 0..=5 | 6..10);
    |                 ^^^^^^^^^^^^^ help: try: `0..10`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:39:17
+  --> tests/ui/manual_range_patterns.rs:40:17
    |
 LL |     matches!(f, 0..5 | 5..=10);
    |                 ^^^^^^^^^^^^^ help: try: `0..=10`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:40:17
+  --> tests/ui/manual_range_patterns.rs:41:17
    |
 LL |     matches!(f, 5..=10 | 0..5);
    |                 ^^^^^^^^^^^^^ help: try: `0..=10`
 
 error: this OR pattern can be rewritten using a range
-  --> tests/ui/manual_range_patterns.rs:44:26
+  --> tests/ui/manual_range_patterns.rs:45:26
    |
 LL |             matches!($e, 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10)
    |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10`
diff --git a/tests/ui/half-open-range-patterns/half-open-range-pats-exhaustive-fail.rs b/tests/ui/half-open-range-patterns/half-open-range-pats-exhaustive-fail.rs
index 33b99259dfe..9d067229b6d 100644
--- a/tests/ui/half-open-range-patterns/half-open-range-pats-exhaustive-fail.rs
+++ b/tests/ui/half-open-range-patterns/half-open-range-pats-exhaustive-fail.rs
@@ -1,6 +1,7 @@
 // Test various non-exhaustive matches for `X..`, `..=X` and `..X` ranges.
 
 #![feature(exclusive_range_pattern)]
+#![allow(non_contiguous_range_endpoints)]
 
 fn main() {}
 
diff --git a/tests/ui/half-open-range-patterns/half-open-range-pats-exhaustive-fail.stderr b/tests/ui/half-open-range-patterns/half-open-range-pats-exhaustive-fail.stderr
index 1e68235303b..6b20a820b73 100644
--- a/tests/ui/half-open-range-patterns/half-open-range-pats-exhaustive-fail.stderr
+++ b/tests/ui/half-open-range-patterns/half-open-range-pats-exhaustive-fail.stderr
@@ -1,5 +1,5 @@
 error[E0004]: non-exhaustive patterns: `_` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:14:8
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:15:8
    |
 LL |     m!(0f32, f32::NEG_INFINITY..);
    |        ^^^^ pattern `_` not covered
@@ -11,7 +11,7 @@ LL |         match $s { $($t)+ => {}, _ => todo!() }
    |                                ++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `_` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:15:8
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:16:8
    |
 LL |     m!(0f32, ..f32::INFINITY);
    |        ^^^^ pattern `_` not covered
@@ -23,7 +23,7 @@ LL |         match $s { $($t)+ => {}, _ => todo!() }
    |                                ++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `'\u{10ffff}'` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:24:8
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:25:8
    |
 LL |     m!('a', ..core::char::MAX);
    |        ^^^ pattern `'\u{10ffff}'` not covered
@@ -35,7 +35,7 @@ LL |         match $s { $($t)+ => {}, '\u{10ffff}' => todo!() }
    |                                +++++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `'\u{10fffe}'..='\u{10ffff}'` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:25:8
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:26:8
    |
 LL |     m!('a', ..ALMOST_MAX);
    |        ^^^ pattern `'\u{10fffe}'..='\u{10ffff}'` not covered
@@ -47,7 +47,7 @@ LL |         match $s { $($t)+ => {}, '\u{10fffe}'..='\u{10ffff}' => todo!() }
    |                                ++++++++++++++++++++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `'\0'` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:26:8
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:27:8
    |
 LL |     m!('a', ALMOST_MIN..);
    |        ^^^ pattern `'\0'` not covered
@@ -59,7 +59,7 @@ LL |         match $s { $($t)+ => {}, '\0' => todo!() }
    |                                +++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `'\u{10ffff}'` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:27:8
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:28:8
    |
 LL |     m!('a', ..=ALMOST_MAX);
    |        ^^^ pattern `'\u{10ffff}'` not covered
@@ -71,7 +71,7 @@ LL |         match $s { $($t)+ => {}, '\u{10ffff}' => todo!() }
    |                                +++++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `'b'` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:28:8
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:29:8
    |
 LL |     m!('a', ..=VAL | VAL_2..);
    |        ^^^ pattern `'b'` not covered
@@ -83,7 +83,7 @@ LL |         match $s { $($t)+ => {}, 'b' => todo!() }
    |                                ++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `'b'` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:29:8
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:30:8
    |
 LL |     m!('a', ..VAL_1 | VAL_2..);
    |        ^^^ pattern `'b'` not covered
@@ -95,7 +95,7 @@ LL |         match $s { $($t)+ => {}, 'b' => todo!() }
    |                                ++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `u8::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:39:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:40:12
    |
 LL |         m!(0, ..u8::MAX);
    |            ^ pattern `u8::MAX` not covered
@@ -107,7 +107,7 @@ LL |         match $s { $($t)+ => {}, u8::MAX => todo!() }
    |                                ++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `254_u8..=u8::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:40:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:41:12
    |
 LL |         m!(0, ..ALMOST_MAX);
    |            ^ pattern `254_u8..=u8::MAX` not covered
@@ -119,7 +119,7 @@ LL |         match $s { $($t)+ => {}, 254_u8..=u8::MAX => todo!() }
    |                                +++++++++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `0_u8` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:41:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:42:12
    |
 LL |         m!(0, ALMOST_MIN..);
    |            ^ pattern `0_u8` not covered
@@ -131,7 +131,7 @@ LL |         match $s { $($t)+ => {}, 0_u8 => todo!() }
    |                                +++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `u8::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:42:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:43:12
    |
 LL |         m!(0, ..=ALMOST_MAX);
    |            ^ pattern `u8::MAX` not covered
@@ -143,7 +143,7 @@ LL |         match $s { $($t)+ => {}, u8::MAX => todo!() }
    |                                ++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_u8` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:43:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:44:12
    |
 LL |         m!(0, ..=VAL | VAL_2..);
    |            ^ pattern `43_u8` not covered
@@ -155,7 +155,7 @@ LL |         match $s { $($t)+ => {}, 43_u8 => todo!() }
    |                                ++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_u8` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:44:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:45:12
    |
 LL |         m!(0, ..VAL_1 | VAL_2..);
    |            ^ pattern `43_u8` not covered
@@ -167,7 +167,7 @@ LL |         match $s { $($t)+ => {}, 43_u8 => todo!() }
    |                                ++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `u16::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:52:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:53:12
    |
 LL |         m!(0, ..u16::MAX);
    |            ^ pattern `u16::MAX` not covered
@@ -179,7 +179,7 @@ LL |         match $s { $($t)+ => {}, u16::MAX => todo!() }
    |                                +++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `65534_u16..=u16::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:53:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:54:12
    |
 LL |         m!(0, ..ALMOST_MAX);
    |            ^ pattern `65534_u16..=u16::MAX` not covered
@@ -191,7 +191,7 @@ LL |         match $s { $($t)+ => {}, 65534_u16..=u16::MAX => todo!() }
    |                                +++++++++++++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `0_u16` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:54:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:55:12
    |
 LL |         m!(0, ALMOST_MIN..);
    |            ^ pattern `0_u16` not covered
@@ -203,7 +203,7 @@ LL |         match $s { $($t)+ => {}, 0_u16 => todo!() }
    |                                ++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `u16::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:55:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:56:12
    |
 LL |         m!(0, ..=ALMOST_MAX);
    |            ^ pattern `u16::MAX` not covered
@@ -215,7 +215,7 @@ LL |         match $s { $($t)+ => {}, u16::MAX => todo!() }
    |                                +++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_u16` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:56:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:57:12
    |
 LL |         m!(0, ..=VAL | VAL_2..);
    |            ^ pattern `43_u16` not covered
@@ -227,7 +227,7 @@ LL |         match $s { $($t)+ => {}, 43_u16 => todo!() }
    |                                +++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_u16` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:57:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:58:12
    |
 LL |         m!(0, ..VAL_1 | VAL_2..);
    |            ^ pattern `43_u16` not covered
@@ -239,7 +239,7 @@ LL |         match $s { $($t)+ => {}, 43_u16 => todo!() }
    |                                +++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `u32::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:65:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:66:12
    |
 LL |         m!(0, ..u32::MAX);
    |            ^ pattern `u32::MAX` not covered
@@ -251,7 +251,7 @@ LL |         match $s { $($t)+ => {}, u32::MAX => todo!() }
    |                                +++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `4294967294_u32..=u32::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:66:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:67:12
    |
 LL |         m!(0, ..ALMOST_MAX);
    |            ^ pattern `4294967294_u32..=u32::MAX` not covered
@@ -263,7 +263,7 @@ LL |         match $s { $($t)+ => {}, 4294967294_u32..=u32::MAX => todo!() }
    |                                ++++++++++++++++++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `0_u32` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:67:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:68:12
    |
 LL |         m!(0, ALMOST_MIN..);
    |            ^ pattern `0_u32` not covered
@@ -275,7 +275,7 @@ LL |         match $s { $($t)+ => {}, 0_u32 => todo!() }
    |                                ++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `u32::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:68:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:69:12
    |
 LL |         m!(0, ..=ALMOST_MAX);
    |            ^ pattern `u32::MAX` not covered
@@ -287,7 +287,7 @@ LL |         match $s { $($t)+ => {}, u32::MAX => todo!() }
    |                                +++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_u32` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:69:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:70:12
    |
 LL |         m!(0, ..=VAL | VAL_2..);
    |            ^ pattern `43_u32` not covered
@@ -299,7 +299,7 @@ LL |         match $s { $($t)+ => {}, 43_u32 => todo!() }
    |                                +++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_u32` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:70:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:71:12
    |
 LL |         m!(0, ..VAL_1 | VAL_2..);
    |            ^ pattern `43_u32` not covered
@@ -311,7 +311,7 @@ LL |         match $s { $($t)+ => {}, 43_u32 => todo!() }
    |                                +++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `u64::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:78:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:79:12
    |
 LL |         m!(0, ..u64::MAX);
    |            ^ pattern `u64::MAX` not covered
@@ -323,7 +323,7 @@ LL |         match $s { $($t)+ => {}, u64::MAX => todo!() }
    |                                +++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `18446744073709551614_u64..=u64::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:79:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:80:12
    |
 LL |         m!(0, ..ALMOST_MAX);
    |            ^ pattern `18446744073709551614_u64..=u64::MAX` not covered
@@ -335,7 +335,7 @@ LL |         match $s { $($t)+ => {}, 18446744073709551614_u64..=u64::MAX => tod
    |                                ++++++++++++++++++++++++++++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `0_u64` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:80:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:81:12
    |
 LL |         m!(0, ALMOST_MIN..);
    |            ^ pattern `0_u64` not covered
@@ -347,7 +347,7 @@ LL |         match $s { $($t)+ => {}, 0_u64 => todo!() }
    |                                ++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `u64::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:81:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:82:12
    |
 LL |         m!(0, ..=ALMOST_MAX);
    |            ^ pattern `u64::MAX` not covered
@@ -359,7 +359,7 @@ LL |         match $s { $($t)+ => {}, u64::MAX => todo!() }
    |                                +++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_u64` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:82:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:83:12
    |
 LL |         m!(0, ..=VAL | VAL_2..);
    |            ^ pattern `43_u64` not covered
@@ -371,7 +371,7 @@ LL |         match $s { $($t)+ => {}, 43_u64 => todo!() }
    |                                +++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_u64` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:83:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:84:12
    |
 LL |         m!(0, ..VAL_1 | VAL_2..);
    |            ^ pattern `43_u64` not covered
@@ -383,7 +383,7 @@ LL |         match $s { $($t)+ => {}, 43_u64 => todo!() }
    |                                +++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `u128::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:91:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:92:12
    |
 LL |         m!(0, ..u128::MAX);
    |            ^ pattern `u128::MAX` not covered
@@ -395,7 +395,7 @@ LL |         match $s { $($t)+ => {}, u128::MAX => todo!() }
    |                                ++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `340282366920938463463374607431768211454_u128..=u128::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:92:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:93:12
    |
 LL |         m!(0, ..ALMOST_MAX);
    |            ^ pattern `340282366920938463463374607431768211454_u128..=u128::MAX` not covered
@@ -407,7 +407,7 @@ LL |         match $s { $($t)+ => {}, 340282366920938463463374607431768211454_u1
    |                                +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `0_u128` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:93:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:94:12
    |
 LL |         m!(0, ALMOST_MIN..);
    |            ^ pattern `0_u128` not covered
@@ -419,7 +419,7 @@ LL |         match $s { $($t)+ => {}, 0_u128 => todo!() }
    |                                +++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `u128::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:94:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:95:12
    |
 LL |         m!(0, ..=ALMOST_MAX);
    |            ^ pattern `u128::MAX` not covered
@@ -431,7 +431,7 @@ LL |         match $s { $($t)+ => {}, u128::MAX => todo!() }
    |                                ++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_u128` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:95:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:96:12
    |
 LL |         m!(0, ..=VAL | VAL_2..);
    |            ^ pattern `43_u128` not covered
@@ -443,7 +443,7 @@ LL |         match $s { $($t)+ => {}, 43_u128 => todo!() }
    |                                ++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_u128` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:96:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:97:12
    |
 LL |         m!(0, ..VAL_1 | VAL_2..);
    |            ^ pattern `43_u128` not covered
@@ -455,7 +455,7 @@ LL |         match $s { $($t)+ => {}, 43_u128 => todo!() }
    |                                ++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i8::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:107:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:108:12
    |
 LL |         m!(0, ..i8::MAX);
    |            ^ pattern `i8::MAX` not covered
@@ -467,7 +467,7 @@ LL |         match $s { $($t)+ => {}, i8::MAX => todo!() }
    |                                ++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `126_i8..=i8::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:108:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:109:12
    |
 LL |         m!(0, ..ALMOST_MAX);
    |            ^ pattern `126_i8..=i8::MAX` not covered
@@ -479,7 +479,7 @@ LL |         match $s { $($t)+ => {}, 126_i8..=i8::MAX => todo!() }
    |                                +++++++++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i8::MIN` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:109:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:110:12
    |
 LL |         m!(0, ALMOST_MIN..);
    |            ^ pattern `i8::MIN` not covered
@@ -491,7 +491,7 @@ LL |         match $s { $($t)+ => {}, i8::MIN => todo!() }
    |                                ++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i8::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:110:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:111:12
    |
 LL |         m!(0, ..=ALMOST_MAX);
    |            ^ pattern `i8::MAX` not covered
@@ -503,7 +503,7 @@ LL |         match $s { $($t)+ => {}, i8::MAX => todo!() }
    |                                ++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_i8` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:111:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:112:12
    |
 LL |         m!(0, ..=VAL | VAL_2..);
    |            ^ pattern `43_i8` not covered
@@ -515,7 +515,7 @@ LL |         match $s { $($t)+ => {}, 43_i8 => todo!() }
    |                                ++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_i8` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:112:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:113:12
    |
 LL |         m!(0, ..VAL_1 | VAL_2..);
    |            ^ pattern `43_i8` not covered
@@ -527,7 +527,7 @@ LL |         match $s { $($t)+ => {}, 43_i8 => todo!() }
    |                                ++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i16::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:120:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:121:12
    |
 LL |         m!(0, ..i16::MAX);
    |            ^ pattern `i16::MAX` not covered
@@ -539,7 +539,7 @@ LL |         match $s { $($t)+ => {}, i16::MAX => todo!() }
    |                                +++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `32766_i16..=i16::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:121:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:122:12
    |
 LL |         m!(0, ..ALMOST_MAX);
    |            ^ pattern `32766_i16..=i16::MAX` not covered
@@ -551,7 +551,7 @@ LL |         match $s { $($t)+ => {}, 32766_i16..=i16::MAX => todo!() }
    |                                +++++++++++++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i16::MIN` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:122:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:123:12
    |
 LL |         m!(0, ALMOST_MIN..);
    |            ^ pattern `i16::MIN` not covered
@@ -563,7 +563,7 @@ LL |         match $s { $($t)+ => {}, i16::MIN => todo!() }
    |                                +++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i16::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:123:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:124:12
    |
 LL |         m!(0, ..=ALMOST_MAX);
    |            ^ pattern `i16::MAX` not covered
@@ -575,7 +575,7 @@ LL |         match $s { $($t)+ => {}, i16::MAX => todo!() }
    |                                +++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_i16` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:124:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:125:12
    |
 LL |         m!(0, ..=VAL | VAL_2..);
    |            ^ pattern `43_i16` not covered
@@ -587,7 +587,7 @@ LL |         match $s { $($t)+ => {}, 43_i16 => todo!() }
    |                                +++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_i16` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:125:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:126:12
    |
 LL |         m!(0, ..VAL_1 | VAL_2..);
    |            ^ pattern `43_i16` not covered
@@ -599,7 +599,7 @@ LL |         match $s { $($t)+ => {}, 43_i16 => todo!() }
    |                                +++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i32::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:133:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:134:12
    |
 LL |         m!(0, ..i32::MAX);
    |            ^ pattern `i32::MAX` not covered
@@ -611,7 +611,7 @@ LL |         match $s { $($t)+ => {}, i32::MAX => todo!() }
    |                                +++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `2147483646_i32..=i32::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:134:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:135:12
    |
 LL |         m!(0, ..ALMOST_MAX);
    |            ^ pattern `2147483646_i32..=i32::MAX` not covered
@@ -623,7 +623,7 @@ LL |         match $s { $($t)+ => {}, 2147483646_i32..=i32::MAX => todo!() }
    |                                ++++++++++++++++++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i32::MIN` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:135:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:136:12
    |
 LL |         m!(0, ALMOST_MIN..);
    |            ^ pattern `i32::MIN` not covered
@@ -635,7 +635,7 @@ LL |         match $s { $($t)+ => {}, i32::MIN => todo!() }
    |                                +++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i32::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:136:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:137:12
    |
 LL |         m!(0, ..=ALMOST_MAX);
    |            ^ pattern `i32::MAX` not covered
@@ -647,7 +647,7 @@ LL |         match $s { $($t)+ => {}, i32::MAX => todo!() }
    |                                +++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_i32` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:137:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:138:12
    |
 LL |         m!(0, ..=VAL | VAL_2..);
    |            ^ pattern `43_i32` not covered
@@ -659,7 +659,7 @@ LL |         match $s { $($t)+ => {}, 43_i32 => todo!() }
    |                                +++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_i32` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:138:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:139:12
    |
 LL |         m!(0, ..VAL_1 | VAL_2..);
    |            ^ pattern `43_i32` not covered
@@ -671,7 +671,7 @@ LL |         match $s { $($t)+ => {}, 43_i32 => todo!() }
    |                                +++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i64::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:146:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:147:12
    |
 LL |         m!(0, ..i64::MAX);
    |            ^ pattern `i64::MAX` not covered
@@ -683,7 +683,7 @@ LL |         match $s { $($t)+ => {}, i64::MAX => todo!() }
    |                                +++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `9223372036854775806_i64..=i64::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:147:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:148:12
    |
 LL |         m!(0, ..ALMOST_MAX);
    |            ^ pattern `9223372036854775806_i64..=i64::MAX` not covered
@@ -695,7 +695,7 @@ LL |         match $s { $($t)+ => {}, 9223372036854775806_i64..=i64::MAX => todo
    |                                +++++++++++++++++++++++++++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i64::MIN` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:148:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:149:12
    |
 LL |         m!(0, ALMOST_MIN..);
    |            ^ pattern `i64::MIN` not covered
@@ -707,7 +707,7 @@ LL |         match $s { $($t)+ => {}, i64::MIN => todo!() }
    |                                +++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i64::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:149:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:150:12
    |
 LL |         m!(0, ..=ALMOST_MAX);
    |            ^ pattern `i64::MAX` not covered
@@ -719,7 +719,7 @@ LL |         match $s { $($t)+ => {}, i64::MAX => todo!() }
    |                                +++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_i64` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:150:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:151:12
    |
 LL |         m!(0, ..=VAL | VAL_2..);
    |            ^ pattern `43_i64` not covered
@@ -731,7 +731,7 @@ LL |         match $s { $($t)+ => {}, 43_i64 => todo!() }
    |                                +++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_i64` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:151:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:152:12
    |
 LL |         m!(0, ..VAL_1 | VAL_2..);
    |            ^ pattern `43_i64` not covered
@@ -743,7 +743,7 @@ LL |         match $s { $($t)+ => {}, 43_i64 => todo!() }
    |                                +++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i128::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:159:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:160:12
    |
 LL |         m!(0, ..i128::MAX);
    |            ^ pattern `i128::MAX` not covered
@@ -755,7 +755,7 @@ LL |         match $s { $($t)+ => {}, i128::MAX => todo!() }
    |                                ++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `170141183460469231731687303715884105726_i128..=i128::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:160:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:161:12
    |
 LL |         m!(0, ..ALMOST_MAX);
    |            ^ pattern `170141183460469231731687303715884105726_i128..=i128::MAX` not covered
@@ -767,7 +767,7 @@ LL |         match $s { $($t)+ => {}, 170141183460469231731687303715884105726_i1
    |                                +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i128::MIN` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:161:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:162:12
    |
 LL |         m!(0, ALMOST_MIN..);
    |            ^ pattern `i128::MIN` not covered
@@ -779,7 +779,7 @@ LL |         match $s { $($t)+ => {}, i128::MIN => todo!() }
    |                                ++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i128::MAX` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:162:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:163:12
    |
 LL |         m!(0, ..=ALMOST_MAX);
    |            ^ pattern `i128::MAX` not covered
@@ -791,7 +791,7 @@ LL |         match $s { $($t)+ => {}, i128::MAX => todo!() }
    |                                ++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_i128` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:163:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:164:12
    |
 LL |         m!(0, ..=VAL | VAL_2..);
    |            ^ pattern `43_i128` not covered
@@ -803,7 +803,7 @@ LL |         match $s { $($t)+ => {}, 43_i128 => todo!() }
    |                                ++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `43_i128` not covered
-  --> $DIR/half-open-range-pats-exhaustive-fail.rs:164:12
+  --> $DIR/half-open-range-pats-exhaustive-fail.rs:165:12
    |
 LL |         m!(0, ..VAL_1 | VAL_2..);
    |            ^ pattern `43_i128` not covered
diff --git a/tests/ui/half-open-range-patterns/range_pat_interactions0.rs b/tests/ui/half-open-range-patterns/range_pat_interactions0.rs
index 7a82f9ce89a..53b6b89ed16 100644
--- a/tests/ui/half-open-range-patterns/range_pat_interactions0.rs
+++ b/tests/ui/half-open-range-patterns/range_pat_interactions0.rs
@@ -1,4 +1,5 @@
 //@ run-pass
+#![allow(non_contiguous_range_endpoints)]
 #![feature(exclusive_range_pattern)]
 #![feature(inline_const_pat)]
 
diff --git a/tests/ui/match/issue-18060.rs b/tests/ui/match/issue-18060.rs
index daba393a3fa..9d480ba6b8b 100644
--- a/tests/ui/match/issue-18060.rs
+++ b/tests/ui/match/issue-18060.rs
@@ -1,6 +1,7 @@
 //@ run-pass
 // Regression test for #18060: match arms were matching in the wrong order.
 
+#[allow(non_contiguous_range_endpoints)]
 fn main() {
     assert_eq!(2, match (1, 3) { (0, 2..=5) => 1, (1, 3) => 2, (_, 2..=5) => 3, (_, _) => 4 });
     assert_eq!(2, match (1, 3) {                  (1, 3) => 2, (_, 2..=5) => 3, (_, _) => 4 });
diff --git a/tests/ui/mir/mir_match_test.rs b/tests/ui/mir/mir_match_test.rs
index 925e1e6ab3d..0da8522f218 100644
--- a/tests/ui/mir/mir_match_test.rs
+++ b/tests/ui/mir/mir_match_test.rs
@@ -1,5 +1,6 @@
 #![feature(exclusive_range_pattern)]
 #![allow(overlapping_range_endpoints)]
+#![allow(non_contiguous_range_endpoints)]
 
 //@ run-pass
 
diff --git a/tests/ui/pattern/usefulness/integer-ranges/exhaustiveness.rs b/tests/ui/pattern/usefulness/integer-ranges/exhaustiveness.rs
index 0f5f49c4ca4..07156d9a08a 100644
--- a/tests/ui/pattern/usefulness/integer-ranges/exhaustiveness.rs
+++ b/tests/ui/pattern/usefulness/integer-ranges/exhaustiveness.rs
@@ -1,5 +1,6 @@
 #![feature(exclusive_range_pattern)]
 #![allow(overlapping_range_endpoints)]
+#![allow(non_contiguous_range_endpoints)]
 #![deny(unreachable_patterns)]
 
 macro_rules! m {
diff --git a/tests/ui/pattern/usefulness/integer-ranges/exhaustiveness.stderr b/tests/ui/pattern/usefulness/integer-ranges/exhaustiveness.stderr
index b585de20629..90d0fd7483a 100644
--- a/tests/ui/pattern/usefulness/integer-ranges/exhaustiveness.stderr
+++ b/tests/ui/pattern/usefulness/integer-ranges/exhaustiveness.stderr
@@ -1,5 +1,5 @@
 error[E0004]: non-exhaustive patterns: `u8::MAX` not covered
-  --> $DIR/exhaustiveness.rs:47:8
+  --> $DIR/exhaustiveness.rs:48:8
    |
 LL |     m!(0u8, 0..255);
    |        ^^^ pattern `u8::MAX` not covered
@@ -11,7 +11,7 @@ LL |         match $s { $($t)+ => {}, u8::MAX => todo!() }
    |                                ++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `u8::MAX` not covered
-  --> $DIR/exhaustiveness.rs:48:8
+  --> $DIR/exhaustiveness.rs:49:8
    |
 LL |     m!(0u8, 0..=254);
    |        ^^^ pattern `u8::MAX` not covered
@@ -23,7 +23,7 @@ LL |         match $s { $($t)+ => {}, u8::MAX => todo!() }
    |                                ++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `0_u8` not covered
-  --> $DIR/exhaustiveness.rs:49:8
+  --> $DIR/exhaustiveness.rs:50:8
    |
 LL |     m!(0u8, 1..=255);
    |        ^^^ pattern `0_u8` not covered
@@ -35,7 +35,7 @@ LL |         match $s { $($t)+ => {}, 0_u8 => todo!() }
    |                                +++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `42_u8` not covered
-  --> $DIR/exhaustiveness.rs:50:8
+  --> $DIR/exhaustiveness.rs:51:8
    |
 LL |     m!(0u8, 0..42 | 43..=255);
    |        ^^^ pattern `42_u8` not covered
@@ -47,7 +47,7 @@ LL |         match $s { $($t)+ => {}, 42_u8 => todo!() }
    |                                ++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i8::MAX` not covered
-  --> $DIR/exhaustiveness.rs:51:8
+  --> $DIR/exhaustiveness.rs:52:8
    |
 LL |     m!(0i8, -128..127);
    |        ^^^ pattern `i8::MAX` not covered
@@ -59,7 +59,7 @@ LL |         match $s { $($t)+ => {}, i8::MAX => todo!() }
    |                                ++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i8::MAX` not covered
-  --> $DIR/exhaustiveness.rs:52:8
+  --> $DIR/exhaustiveness.rs:53:8
    |
 LL |     m!(0i8, -128..=126);
    |        ^^^ pattern `i8::MAX` not covered
@@ -71,7 +71,7 @@ LL |         match $s { $($t)+ => {}, i8::MAX => todo!() }
    |                                ++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `i8::MIN` not covered
-  --> $DIR/exhaustiveness.rs:53:8
+  --> $DIR/exhaustiveness.rs:54:8
    |
 LL |     m!(0i8, -127..=127);
    |        ^^^ pattern `i8::MIN` not covered
@@ -83,7 +83,7 @@ LL |         match $s { $($t)+ => {}, i8::MIN => todo!() }
    |                                ++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `0_i8` not covered
-  --> $DIR/exhaustiveness.rs:54:11
+  --> $DIR/exhaustiveness.rs:55:11
    |
 LL |     match 0i8 {
    |           ^^^ pattern `0_i8` not covered
@@ -96,7 +96,7 @@ LL +         0_i8 => todo!()
    |
 
 error[E0004]: non-exhaustive patterns: `u128::MAX` not covered
-  --> $DIR/exhaustiveness.rs:59:8
+  --> $DIR/exhaustiveness.rs:60:8
    |
 LL |     m!(0u128, 0..=ALMOST_MAX);
    |        ^^^^^ pattern `u128::MAX` not covered
@@ -108,7 +108,7 @@ LL |         match $s { $($t)+ => {}, u128::MAX => todo!() }
    |                                ++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `5_u128..=u128::MAX` not covered
-  --> $DIR/exhaustiveness.rs:60:8
+  --> $DIR/exhaustiveness.rs:61:8
    |
 LL |     m!(0u128, 0..=4);
    |        ^^^^^ pattern `5_u128..=u128::MAX` not covered
@@ -120,7 +120,7 @@ LL |         match $s { $($t)+ => {}, 5_u128..=u128::MAX => todo!() }
    |                                +++++++++++++++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `0_u128` not covered
-  --> $DIR/exhaustiveness.rs:61:8
+  --> $DIR/exhaustiveness.rs:62:8
    |
 LL |     m!(0u128, 1..=u128::MAX);
    |        ^^^^^ pattern `0_u128` not covered
@@ -132,7 +132,7 @@ LL |         match $s { $($t)+ => {}, 0_u128 => todo!() }
    |                                +++++++++++++++++++
 
 error[E0004]: non-exhaustive patterns: `(126_u8..=127_u8, false)` not covered
-  --> $DIR/exhaustiveness.rs:69:11
+  --> $DIR/exhaustiveness.rs:70:11
    |
 LL |     match (0u8, true) {
    |           ^^^^^^^^^^^ pattern `(126_u8..=127_u8, false)` not covered
diff --git a/tests/ui/pattern/usefulness/integer-ranges/gap_between_ranges.rs b/tests/ui/pattern/usefulness/integer-ranges/gap_between_ranges.rs
new file mode 100644
index 00000000000..78849d7e143
--- /dev/null
+++ b/tests/ui/pattern/usefulness/integer-ranges/gap_between_ranges.rs
@@ -0,0 +1,117 @@
+#![feature(exclusive_range_pattern)]
+#![deny(non_contiguous_range_endpoints)]
+
+macro_rules! m {
+    ($s:expr, $t1:pat, $t2:pat) => {
+        match $s {
+            $t1 => {}
+            $t2 => {}
+            _ => {}
+        }
+    };
+}
+
+fn main() {
+    match 0u8 {
+        20..30 => {} //~ ERROR multiple ranges are one apart
+        31..=40 => {}
+        _ => {}
+    }
+    match 0u8 {
+        20..30 => {} //~ ERROR multiple ranges are one apart
+        31 => {}
+        _ => {}
+    }
+
+    m!(0u8, 20..30, 31..=40); //~ ERROR multiple ranges are one apart
+    m!(0u8, 31..=40, 20..30); //~ ERROR multiple ranges are one apart
+    m!(0u8, 20..30, 29..=40); //~ WARN multiple patterns overlap on their endpoints
+    m!(0u8, 20..30, 30..=40);
+    m!(0u8, 20..30, 31..=40); //~ ERROR multiple ranges are one apart
+    m!(0u8, 20..30, 32..=40);
+    m!(0u8, 20..30, 31..=32); //~ ERROR multiple ranges are one apart
+    // Don't lint two singletons.
+    m!(0u8, 30, 32);
+    // Don't lint on the equivalent inclusive range
+    m!(0u8, 20..=29, 31..=40);
+    // Don't lint if the lower one is a singleton.
+    m!(0u8, 30, 32..=40);
+
+    // Catch the `lo..MAX` case.
+    match 0u8 {
+        0..255 => {} //~ ERROR exclusive range missing `u8::MAX`
+        _ => {}
+    }
+    // Except for `usize`, since `0..=usize::MAX` isn't exhaustive either.
+    match 0usize {
+        0..usize::MAX => {}
+        _ => {}
+    }
+
+    // Don't lint if the gap is caught by another range.
+    match 0u8 {
+        0..10 => {}
+        11..20 => {}
+        10 => {}
+        _ => {}
+    }
+    match 0u8 {
+        0..10 => {}
+        11..20 => {}
+        5..15 => {}
+        _ => {}
+    }
+    match 0u8 {
+        0..255 => {}
+        255 => {}
+    }
+
+    // Test multiple at once
+    match 0u8 {
+        0..10 => {} //~ ERROR multiple ranges are one apart
+        11..20 => {}
+        11..30 => {}
+        _ => {}
+    }
+    match 0u8 {
+        0..10 => {}  //~ ERROR multiple ranges are one apart
+        11..20 => {} //~ ERROR multiple ranges are one apart
+        21..30 => {}
+        _ => {}
+    }
+    match 0u8 {
+        00..20 => {} //~ ERROR multiple ranges are one apart
+        10..20 => {} //~ ERROR multiple ranges are one apart
+        21..30 => {}
+        21..40 => {}
+        _ => {}
+    }
+
+    // Test nested
+    match (0u8, true) {
+        (0..10, true) => {} //~ ERROR multiple ranges are one apart
+        (11..20, true) => {}
+        _ => {}
+    }
+    match (true, 0u8) {
+        (true, 0..10) => {} //~ ERROR multiple ranges are one apart
+        (true, 11..20) => {}
+        _ => {}
+    }
+    // Asymmetry due to how exhaustiveness is computed.
+    match (0u8, true) {
+        (0..10, true) => {} //~ ERROR multiple ranges are one apart
+        (11..20, false) => {}
+        _ => {}
+    }
+    match (true, 0u8) {
+        (true, 0..10) => {}
+        (false, 11..20) => {}
+        _ => {}
+    }
+    match Some(0u8) {
+        Some(0..10) => {} //~ ERROR multiple ranges are one apart
+        Some(11..20) => {}
+        _ => {}
+    }
+}
diff --git a/tests/ui/pattern/usefulness/integer-ranges/gap_between_ranges.stderr b/tests/ui/pattern/usefulness/integer-ranges/gap_between_ranges.stderr
new file mode 100644
index 00000000000..e5c2d788ba4
--- /dev/null
+++ b/tests/ui/pattern/usefulness/integer-ranges/gap_between_ranges.stderr
@@ -0,0 +1,193 @@
+error: multiple ranges are one apart
+  --> $DIR/gap_between_ranges.rs:16:9
+   |
+LL |         20..30 => {}
+   |         ^^^^^^
+   |         |
+   |         this range doesn't match `30_u8` because `..` is an exclusive range
+   |         help: use an inclusive range instead: `20_u8..=30_u8`
+LL |         31..=40 => {}
+   |         ------- this could appear to continue range `20_u8..30_u8`, but `30_u8` isn't matched by either of them
+   |
+note: the lint level is defined here
+  --> $DIR/gap_between_ranges.rs:2:9
+   |
+LL | #![deny(non_contiguous_range_endpoints)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: multiple ranges are one apart
+  --> $DIR/gap_between_ranges.rs:21:9
+   |
+LL |         20..30 => {}
+   |         ^^^^^^
+   |         |
+   |         this range doesn't match `30_u8` because `..` is an exclusive range
+   |         help: use an inclusive range instead: `20_u8..=30_u8`
+LL |         31 => {}
+   |         -- this could appear to continue range `20_u8..30_u8`, but `30_u8` isn't matched by either of them
+
+error: multiple ranges are one apart
+  --> $DIR/gap_between_ranges.rs:26:13
+   |
+LL |     m!(0u8, 20..30, 31..=40);
+   |             ^^^^^^  ------- this could appear to continue range `20_u8..30_u8`, but `30_u8` isn't matched by either of them
+   |             |
+   |             this range doesn't match `30_u8` because `..` is an exclusive range
+   |             help: use an inclusive range instead: `20_u8..=30_u8`
+
+error: multiple ranges are one apart
+  --> $DIR/gap_between_ranges.rs:27:22
+   |
+LL |     m!(0u8, 31..=40, 20..30);
+   |             -------  ^^^^^^
+   |             |        |
+   |             |        this range doesn't match `30_u8` because `..` is an exclusive range
+   |             |        help: use an inclusive range instead: `20_u8..=30_u8`
+   |             this could appear to continue range `20_u8..30_u8`, but `30_u8` isn't matched by either of them
+
+warning: multiple patterns overlap on their endpoints
+  --> $DIR/gap_between_ranges.rs:28:21
+   |
+LL |     m!(0u8, 20..30, 29..=40);
+   |             ------  ^^^^^^^ ... with this range
+   |             |
+   |             this range overlaps on `29_u8`...
+   |
+   = note: you likely meant to write mutually exclusive ranges
+   = note: `#[warn(overlapping_range_endpoints)]` on by default
+
+error: multiple ranges are one apart
+  --> $DIR/gap_between_ranges.rs:30:13
+   |
+LL |     m!(0u8, 20..30, 31..=40);
+   |             ^^^^^^  ------- this could appear to continue range `20_u8..30_u8`, but `30_u8` isn't matched by either of them
+   |             |
+   |             this range doesn't match `30_u8` because `..` is an exclusive range
+   |             help: use an inclusive range instead: `20_u8..=30_u8`
+
+error: multiple ranges are one apart
+  --> $DIR/gap_between_ranges.rs:32:13
+   |
+LL |     m!(0u8, 20..30, 31..=32);
+   |             ^^^^^^  ------- this could appear to continue range `20_u8..30_u8`, but `30_u8` isn't matched by either of them
+   |             |
+   |             this range doesn't match `30_u8` because `..` is an exclusive range
+   |             help: use an inclusive range instead: `20_u8..=30_u8`
+
+error: exclusive range missing `u8::MAX`
+  --> $DIR/gap_between_ranges.rs:42:9
+   |
+LL |         0..255 => {}
+   |         ^^^^^^
+   |         |
+   |         this range doesn't match `u8::MAX` because `..` is an exclusive range
+   |         help: use an inclusive range instead: `0_u8..=u8::MAX`
+
+error: multiple ranges are one apart
+  --> $DIR/gap_between_ranges.rs:71:9
+   |
+LL |         0..10 => {}
+   |         ^^^^^
+   |         |
+   |         this range doesn't match `10_u8` because `..` is an exclusive range
+   |         help: use an inclusive range instead: `0_u8..=10_u8`
+LL |         11..20 => {}
+   |         ------ this could appear to continue range `0_u8..10_u8`, but `10_u8` isn't matched by either of them
+LL |         11..30 => {}
+   |         ------ this could appear to continue range `0_u8..10_u8`, but `10_u8` isn't matched by either of them
+
+error: multiple ranges are one apart
+  --> $DIR/gap_between_ranges.rs:77:9
+   |
+LL |         0..10 => {}
+   |         ^^^^^
+   |         |
+   |         this range doesn't match `10_u8` because `..` is an exclusive range
+   |         help: use an inclusive range instead: `0_u8..=10_u8`
+LL |         11..20 => {}
+   |         ------ this could appear to continue range `0_u8..10_u8`, but `10_u8` isn't matched by either of them
+
+error: multiple ranges are one apart
+  --> $DIR/gap_between_ranges.rs:78:9
+   |
+LL |         11..20 => {}
+   |         ^^^^^^
+   |         |
+   |         this range doesn't match `20_u8` because `..` is an exclusive range
+   |         help: use an inclusive range instead: `11_u8..=20_u8`
+LL |         21..30 => {}
+   |         ------ this could appear to continue range `11_u8..20_u8`, but `20_u8` isn't matched by either of them
+
+error: multiple ranges are one apart
+  --> $DIR/gap_between_ranges.rs:83:9
+   |
+LL |         00..20 => {}
+   |         ^^^^^^
+   |         |
+   |         this range doesn't match `20_u8` because `..` is an exclusive range
+   |         help: use an inclusive range instead: `0_u8..=20_u8`
+LL |         10..20 => {}
+LL |         21..30 => {}
+   |         ------ this could appear to continue range `0_u8..20_u8`, but `20_u8` isn't matched by either of them
+LL |         21..40 => {}
+   |         ------ this could appear to continue range `0_u8..20_u8`, but `20_u8` isn't matched by either of them
+
+error: multiple ranges are one apart
+  --> $DIR/gap_between_ranges.rs:84:9
+   |
+LL |         10..20 => {}
+   |         ^^^^^^
+   |         |
+   |         this range doesn't match `20_u8` because `..` is an exclusive range
+   |         help: use an inclusive range instead: `10_u8..=20_u8`
+LL |         21..30 => {}
+   |         ------ this could appear to continue range `10_u8..20_u8`, but `20_u8` isn't matched by either of them
+LL |         21..40 => {}
+   |         ------ this could appear to continue range `10_u8..20_u8`, but `20_u8` isn't matched by either of them
+
+error: multiple ranges are one apart
+  --> $DIR/gap_between_ranges.rs:92:10
+   |
+LL |         (0..10, true) => {}
+   |          ^^^^^
+   |          |
+   |          this range doesn't match `10_u8` because `..` is an exclusive range
+   |          help: use an inclusive range instead: `0_u8..=10_u8`
+LL |         (11..20, true) => {}
+   |          ------ this could appear to continue range `0_u8..10_u8`, but `10_u8` isn't matched by either of them
+
+error: multiple ranges are one apart
+  --> $DIR/gap_between_ranges.rs:97:16
+   |
+LL |         (true, 0..10) => {}
+   |                ^^^^^
+   |                |
+   |                this range doesn't match `10_u8` because `..` is an exclusive range
+   |                help: use an inclusive range instead: `0_u8..=10_u8`
+LL |         (true, 11..20) => {}
+   |                ------ this could appear to continue range `0_u8..10_u8`, but `10_u8` isn't matched by either of them
+
+error: multiple ranges are one apart
+  --> $DIR/gap_between_ranges.rs:103:10
+   |
+LL |         (0..10, true) => {}
+   |          ^^^^^
+   |          |
+   |          this range doesn't match `10_u8` because `..` is an exclusive range
+   |          help: use an inclusive range instead: `0_u8..=10_u8`
+LL |         (11..20, false) => {}
+   |          ------ this could appear to continue range `0_u8..10_u8`, but `10_u8` isn't matched by either of them
+
+error: multiple ranges are one apart
+  --> $DIR/gap_between_ranges.rs:113:14
+   |
+LL |         Some(0..10) => {}
+   |              ^^^^^
+   |              |
+   |              this range doesn't match `10_u8` because `..` is an exclusive range
+   |              help: use an inclusive range instead: `0_u8..=10_u8`
+LL |         Some(11..20) => {}
+   |              ------ this could appear to continue range `0_u8..10_u8`, but `10_u8` isn't matched by either of them
+
+error: aborting due to 16 previous errors; 1 warning emitted
+
diff --git a/tests/ui/pattern/usefulness/integer-ranges/reachability.rs b/tests/ui/pattern/usefulness/integer-ranges/reachability.rs
index 247fdd91572..13b84e2c44b 100644
--- a/tests/ui/pattern/usefulness/integer-ranges/reachability.rs
+++ b/tests/ui/pattern/usefulness/integer-ranges/reachability.rs
@@ -1,5 +1,6 @@
 #![feature(exclusive_range_pattern)]
 #![allow(overlapping_range_endpoints)]
+#![allow(non_contiguous_range_endpoints)]
 #![deny(unreachable_patterns)]
 
 macro_rules! m {
diff --git a/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr b/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr
index c5b028d2038..0f52dfd83b9 100644
--- a/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr
+++ b/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr
@@ -1,137 +1,137 @@
 error: unreachable pattern
-  --> $DIR/reachability.rs:18:17
+  --> $DIR/reachability.rs:19:17
    |
 LL |     m!(0u8, 42, 42);
    |                 ^^
    |
 note: the lint level is defined here
-  --> $DIR/reachability.rs:3:9
+  --> $DIR/reachability.rs:4:9
    |
 LL | #![deny(unreachable_patterns)]
    |         ^^^^^^^^^^^^^^^^^^^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:22:22
+  --> $DIR/reachability.rs:23:22
    |
 LL |     m!(0u8, 20..=30, 20);
    |                      ^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:23:22
+  --> $DIR/reachability.rs:24:22
    |
 LL |     m!(0u8, 20..=30, 21);
    |                      ^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:24:22
+  --> $DIR/reachability.rs:25:22
    |
 LL |     m!(0u8, 20..=30, 25);
    |                      ^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:25:22
+  --> $DIR/reachability.rs:26:22
    |
 LL |     m!(0u8, 20..=30, 29);
    |                      ^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:26:22
+  --> $DIR/reachability.rs:27:22
    |
 LL |     m!(0u8, 20..=30, 30);
    |                      ^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:29:21
+  --> $DIR/reachability.rs:30:21
    |
 LL |     m!(0u8, 20..30, 20);
    |                     ^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:30:21
+  --> $DIR/reachability.rs:31:21
    |
 LL |     m!(0u8, 20..30, 21);
    |                     ^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:31:21
+  --> $DIR/reachability.rs:32:21
    |
 LL |     m!(0u8, 20..30, 25);
    |                     ^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:32:21
+  --> $DIR/reachability.rs:33:21
    |
 LL |     m!(0u8, 20..30, 29);
    |                     ^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:36:22
+  --> $DIR/reachability.rs:37:22
    |
 LL |     m!(0u8, 20..=30, 20..=30);
    |                      ^^^^^^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:37:22
+  --> $DIR/reachability.rs:38:22
    |
 LL |     m!(0u8, 20.. 30, 20.. 30);
    |                      ^^^^^^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:38:22
+  --> $DIR/reachability.rs:39:22
    |
 LL |     m!(0u8, 20..=30, 20.. 30);
    |                      ^^^^^^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:40:22
+  --> $DIR/reachability.rs:41:22
    |
 LL |     m!(0u8, 20..=30, 21..=30);
    |                      ^^^^^^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:41:22
+  --> $DIR/reachability.rs:42:22
    |
 LL |     m!(0u8, 20..=30, 20..=29);
    |                      ^^^^^^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:43:24
+  --> $DIR/reachability.rs:44:24
    |
 LL |     m!('a', 'A'..='z', 'a'..='z');
    |                        ^^^^^^^^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:50:9
+  --> $DIR/reachability.rs:51:9
    |
 LL |         5..=8 => {},
    |         ^^^^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:56:9
+  --> $DIR/reachability.rs:57:9
    |
 LL |         5..15 => {},
    |         ^^^^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:63:9
+  --> $DIR/reachability.rs:64:9
    |
 LL |         5..25 => {},
    |         ^^^^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:71:9
+  --> $DIR/reachability.rs:72:9
    |
 LL |         5..25 => {},
    |         ^^^^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:77:9
+  --> $DIR/reachability.rs:78:9
    |
 LL |         5..15 => {},
    |         ^^^^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:84:9
+  --> $DIR/reachability.rs:85:9
    |
 LL |         _ => {},
    |         - matches any value
@@ -139,19 +139,19 @@ LL |         '\u{D7FF}'..='\u{E000}' => {},
    |         ^^^^^^^^^^^^^^^^^^^^^^^ unreachable pattern
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:89:9
+  --> $DIR/reachability.rs:90:9
    |
 LL |         '\u{D7FF}'..='\u{E000}' => {},
    |         ^^^^^^^^^^^^^^^^^^^^^^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:105:9
+  --> $DIR/reachability.rs:106:9
    |
 LL |         &FOO => {}
    |         ^^^^
 
 error: unreachable pattern
-  --> $DIR/reachability.rs:106:9
+  --> $DIR/reachability.rs:107:9
    |
 LL |         BAR => {}
    |         ^^^