From: Lucas Ly Ba <[email protected]>

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]>
---
This change was merged into the gccrs repository and is posted here for
upstream visibility and potential drive-by review, as requested by GCC
release managers.
Each commit email contains a link to its details on github from where you can
find the Pull-Request and associated discussions.


Commit on github: 
https://github.com/Rust-GCC/gccrs/commit/5f7a4e198e8ab0fc9dd633931889d41e8ba1f5d6

The commit has NOT been mentioned in any issue.

The commit has been mentioned in the following pull-request(s):
 - https://github.com/Rust-GCC/gccrs/pull/4654

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

diff --git a/gcc/rust/checks/lints/unused/rust-unused-checker.cc 
b/gcc/rust/checks/lints/unused/rust-unused-checker.cc
index 7d875daef..30fb37cfb 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 055d49b19..c8a272041 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 000000000..4afff8a25
--- /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,
+    }
+}

base-commit: 0cbc65882f623c0f6908732e9b645b466223d3ee
-- 
2.54.0

Reply via email to