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