https://gcc.gnu.org/g:548fbf6f2c9c2acb1e6fdde93d217ee042eafd23

commit r17-2243-g548fbf6f2c9c2acb1e6fdde93d217ee042eafd23
Author: Lucas Ly Ba <[email protected]>
Date:   Sun Jun 28 20:30:27 2026 +0200

    gccrs: add non_contiguous_range_endpoints lint
    
    Warn when an exclusive range pattern and the next arm are one value
    apart, such as `20..30` followed by `31..=40`, which silently skips 30.
    
    gcc/rust/ChangeLog:
    
            * checks/lints/unused/rust-unused-checker.cc (UnusedChecker::visit):
            New.
            * checks/lints/unused/rust-unused-checker.h (UnusedChecker::visit):
            New.
    
    gcc/testsuite/ChangeLog:
    
            * rust/compile/non-contiguous-range-endpoints_0.rs: New test.
    
    Signed-off-by: Lucas Ly Ba <[email protected]>

Diff:
---
 .../checks/lints/unused/rust-unused-checker.cc     | 108 +++++++++++++++++++++
 gcc/rust/checks/lints/unused/rust-unused-checker.h |   1 +
 .../compile/non-contiguous-range-endpoints_0.rs    |  20 ++++
 3 files changed, 129 insertions(+)

diff --git a/gcc/rust/checks/lints/unused/rust-unused-checker.cc 
b/gcc/rust/checks/lints/unused/rust-unused-checker.cc
index 7d875daef339..30fb37cfb99b 100644
--- a/gcc/rust/checks/lints/unused/rust-unused-checker.cc
+++ b/gcc/rust/checks/lints/unused/rust-unused-checker.cc
@@ -20,6 +20,7 @@
 #include "rust-hir-expr.h"
 #include "rust-hir-generic-param.h"
 #include "rust-hir-item.h"
+#include "rust-hir-pattern.h"
 
 #include "options.h"
 #include "rust-keyword-values.h"
@@ -215,5 +216,112 @@ UnusedChecker::visit (HIR::StructPatternFieldIdentPat 
&field)
   walk (field);
 }
 
+namespace {
+
+bool
+literal_int_value (const HIR::Literal &lit, bool minus, int64_t &out)
+{
+  if (lit.get_lit_type () != HIR::Literal::LitType::INT)
+    return false;
+
+  std::string digits = lit.as_string ();
+  digits.erase (std::remove (digits.begin (), digits.end (), '_'),
+               digits.end ());
+
+  char *end = nullptr;
+  long long value = std::strtoll (digits.c_str (), &end, 10);
+  if (end == digits.c_str () || *end != '\0')
+    return false;
+
+  out = minus ? -value : value;
+  return true;
+}
+
+bool
+range_bound_int (HIR::RangePatternBound &bound, int64_t &out)
+{
+  if (bound.get_bound_type ()
+      != HIR::RangePatternBound::RangePatternBoundType::LITERAL)
+    return false;
+
+  auto &lit = static_cast<HIR::RangePatternBoundLiteral &> (bound);
+  return literal_int_value (lit.get_literal (), lit.get_has_minus (), out);
+}
+
+} // namespace
+
+void
+UnusedChecker::visit (HIR::MatchExpr &expr)
+{
+  struct Range
+  {
+    int64_t lo;
+    int64_t hi;
+    bool inclusive;
+    location_t locus;
+  };
+  std::vector<Range> ranges;
+  std::vector<int64_t> starts;
+
+  for (auto &match_case : expr.get_match_cases ())
+    {
+      auto &pattern = match_case.get_arm ().get_pattern ();
+      if (!pattern)
+       continue;
+
+      if (pattern->get_pattern_type () == HIR::Pattern::PatternType::RANGE)
+       {
+         auto &range = static_cast<HIR::RangePattern &> (*pattern);
+         int64_t lo, hi;
+         if (range_bound_int (range.get_lower_bound (), lo)
+             && range_bound_int (range.get_upper_bound (), hi))
+           {
+             ranges.push_back (
+               {lo, hi, range.is_inclusive_range (), range.get_locus ()});
+             starts.push_back (lo);
+           }
+       }
+      else if (pattern->get_pattern_type ()
+              == HIR::Pattern::PatternType::LITERAL)
+       {
+         auto &lit = static_cast<HIR::LiteralPattern &> (*pattern);
+         int64_t value;
+         if (literal_int_value (lit.get_literal (), lit.get_has_minus (),
+                                value))
+           starts.push_back (value);
+       }
+    }
+
+  // A value is covered if a range matches it or some arm starts exactly on it.
+  auto covered = [&] (int64_t value) {
+    if (std::find (starts.begin (), starts.end (), value) != starts.end ())
+      return true;
+    for (auto &range : ranges)
+      {
+       int64_t top = range.inclusive ? range.hi : range.hi - 1;
+       if (value >= range.lo && value <= top)
+         return true;
+      }
+    return false;
+  };
+
+  // An exclusive range `lo..hi` leaves `hi` unmatched. If another arm picks up
+  // at `hi + 1`, that single value was almost certainly meant to be included.
+  for (auto &range : ranges)
+    {
+      if (range.inclusive)
+       continue;
+
+      int64_t missed = range.hi;
+      if (!covered (missed)
+         && std::find (starts.begin (), starts.end (), missed + 1)
+              != starts.end ())
+       rust_warning_at (range.locus, OPT_Wunused_variable,
+                        "multiple ranges are one apart");
+    }
+
+  walk (expr);
+}
+
 } // namespace Analysis
 } // namespace Rust
diff --git a/gcc/rust/checks/lints/unused/rust-unused-checker.h 
b/gcc/rust/checks/lints/unused/rust-unused-checker.h
index 055d49b193e1..c8a272041a9d 100644
--- a/gcc/rust/checks/lints/unused/rust-unused-checker.h
+++ b/gcc/rust/checks/lints/unused/rust-unused-checker.h
@@ -49,6 +49,7 @@ private:
   virtual void visit (HIR::Module &mod) override;
   virtual void visit (HIR::LifetimeParam &lft) override;
   virtual void visit (HIR::StructPatternFieldIdentPat &field) override;
+  virtual void visit (HIR::MatchExpr &expr) override;
   virtual void visit_loop_label (HIR::LoopLabel &label) override;
 };
 } // namespace Analysis
diff --git a/gcc/testsuite/rust/compile/non-contiguous-range-endpoints_0.rs 
b/gcc/testsuite/rust/compile/non-contiguous-range-endpoints_0.rs
new file mode 100644
index 000000000000..4afff8a25582
--- /dev/null
+++ b/gcc/testsuite/rust/compile/non-contiguous-range-endpoints_0.rs
@@ -0,0 +1,20 @@
+// { dg-additional-options "-frust-unused-check-2.0" }
+#![feature(no_core, exclusive_range_pattern)]
+#![no_core]
+
+pub fn f(x: u8) -> u8 {
+    match x {
+        20..30 => 1,
+// { dg-warning "multiple ranges are one apart" "" { target *-*-* } .-1 }
+        31..=40 => 2,
+        _ => 0,
+    }
+}
+
+pub fn g(x: u8) -> u8 {
+    match x {
+        0..10 => 1,
+        10..=20 => 2,
+        _ => 0,
+    }
+}

Reply via email to