Skip to content

Commit 894ea82

Browse files
feat: unnecessary_min_max lint
1 parent 95c62ff commit 894ea82

9 files changed

+484
-87
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5799,6 +5799,7 @@ Released 2018-09-13
57995799
[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
58005800
[`unnecessary_literal_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_literal_unwrap
58015801
[`unnecessary_map_on_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_map_on_constructor
5802+
[`unnecessary_min_max`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_min_max
58025803
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
58035804
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
58045805
[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
466466
crate::methods::UNNECESSARY_JOIN_INFO,
467467
crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO,
468468
crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO,
469+
crate::methods::UNNECESSARY_MIN_MAX_INFO,
469470
crate::methods::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO,
470471
crate::methods::UNNECESSARY_SORT_BY_INFO,
471472
crate::methods::UNNECESSARY_TO_OWNED_INFO,

clippy_lints/src/methods/mod.rs

+24
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ mod unnecessary_iter_cloned;
116116
mod unnecessary_join;
117117
mod unnecessary_lazy_eval;
118118
mod unnecessary_literal_unwrap;
119+
mod unnecessary_min_max;
119120
mod unnecessary_result_map_or_else;
120121
mod unnecessary_sort_by;
121122
mod unnecessary_to_owned;
@@ -3961,6 +3962,27 @@ declare_clippy_lint! {
39613962
"cloning an `Option` via `as_ref().cloned()`"
39623963
}
39633964

3965+
declare_clippy_lint! {
3966+
/// ### What it does
3967+
/// Checks for unnecessary calls to `min()` or `max()`
3968+
///
3969+
/// ### Why is this bad?
3970+
///
3971+
/// In these cases it is not necessary to call `min()`
3972+
/// ### Example
3973+
/// ```no_run
3974+
/// let _ = 0.min(7_u32);
3975+
/// ```
3976+
/// Use instead:
3977+
/// ```no_run
3978+
/// let _ = 0;
3979+
/// ```
3980+
#[clippy::version = "1.78.0"]
3981+
pub UNNECESSARY_MIN_MAX,
3982+
complexity,
3983+
"using 'min()/max()' when there is no need for it"
3984+
}
3985+
39643986
declare_clippy_lint! {
39653987
/// ### What it does
39663988
/// Checks for usage of `.map_or_else()` "map closure" for `Result` type.
@@ -4236,6 +4258,7 @@ impl_lint_pass!(Methods => [
42364258
UNNECESSARY_RESULT_MAP_OR_ELSE,
42374259
MANUAL_C_STR_LITERALS,
42384260
UNNECESSARY_GET_THEN_CHECK,
4261+
UNNECESSARY_MIN_MAX,
42394262
]);
42404263

42414264
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4526,6 +4549,7 @@ impl Methods {
45264549
Some(("bytes", recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2),
45274550
_ => {},
45284551
},
4552+
("min" | "max", [arg]) => unnecessary_min_max::check(cx, expr, name, recv, arg),
45294553
("drain", ..) => {
45304554
if let Node::Stmt(Stmt { hir_id: _, kind, .. }) = cx.tcx.parent_hir_node(expr.hir_id)
45314555
&& matches!(kind, StmtKind::Semi(_))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use std::cmp::Ordering;
2+
3+
use super::UNNECESSARY_MIN_MAX;
4+
use clippy_utils::diagnostics::span_lint_and_sugg;
5+
6+
use clippy_utils::consts::{constant, Constant, FullInt};
7+
use clippy_utils::source::snippet;
8+
use hir::Expr;
9+
10+
use rustc_errors::Applicability;
11+
use rustc_hir as hir;
12+
use rustc_lint::LateContext;
13+
14+
use rustc_middle::ty;
15+
use rustc_span::Span;
16+
17+
pub fn check<'tcx>(
18+
cx: &LateContext<'tcx>,
19+
expr: &'tcx Expr<'_>,
20+
name: &str,
21+
recv: &'tcx Expr<'_>,
22+
arg: &'tcx Expr<'_>,
23+
) {
24+
let typeck_results = cx.typeck_results();
25+
if let (Some(left), Some(right)) = (constant(cx, typeck_results, recv), constant(cx, typeck_results, arg)) {
26+
let Some(ord) = Constant::partial_cmp(cx.tcx, typeck_results.expr_ty(recv), &left, &right) else {
27+
return;
28+
};
29+
30+
lint(cx, expr, name, recv.span, arg.span, ord);
31+
} else if let Some(extrema) = detect_extrema(cx, recv) {
32+
let ord = match extrema {
33+
Extrema::Minimum => Ordering::Less,
34+
Extrema::Maximum => Ordering::Greater,
35+
};
36+
lint(cx, expr, name, recv.span, arg.span, ord);
37+
} else if let Some(extrema) = detect_extrema(cx, arg) {
38+
let ord = match extrema {
39+
Extrema::Minimum => Ordering::Greater,
40+
Extrema::Maximum => Ordering::Less,
41+
};
42+
lint(cx, expr, name, recv.span, arg.span, ord);
43+
}
44+
}
45+
46+
fn lint(cx: &LateContext<'_>, expr: &Expr<'_>, name: &str, lhs: Span, rhs: Span, order: Ordering) {
47+
let cmp_str = if order.is_ge() { "smaller" } else { "greater" };
48+
49+
let suggested_value = if (name == "min" && order.is_ge()) || (name == "max" && order.is_le()) {
50+
snippet(cx, rhs, "..")
51+
} else {
52+
snippet(cx, lhs, "..")
53+
};
54+
55+
let msg = format!(
56+
"`{}` is never {} than `{}` and has therefore no effect",
57+
snippet(cx, lhs, ".."),
58+
cmp_str,
59+
snippet(cx, rhs, "..")
60+
);
61+
span_lint_and_sugg(
62+
cx,
63+
UNNECESSARY_MIN_MAX,
64+
expr.span,
65+
&msg,
66+
"try",
67+
suggested_value.to_string(),
68+
Applicability::MachineApplicable,
69+
);
70+
}
71+
72+
#[derive(Debug)]
73+
enum Extrema {
74+
Minimum,
75+
Maximum,
76+
}
77+
fn detect_extrema<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Extrema> {
78+
let ty = cx.typeck_results().expr_ty(expr);
79+
80+
let cv = constant(cx, cx.typeck_results(), expr)?;
81+
82+
match (cv.int_value(cx, ty)?, ty.kind()) {
83+
(FullInt::S(i), &ty::Int(ity)) if i == i128::MIN >> (128 - ity.bit_width()?) => Some(Extrema::Minimum),
84+
(FullInt::S(i), &ty::Int(ity)) if i == i128::MAX >> (128 - ity.bit_width()?) => Some(Extrema::Maximum),
85+
(FullInt::U(i), &ty::Uint(uty)) if i == u128::MAX >> (128 - uty.bit_width()?) => Some(Extrema::Maximum),
86+
(FullInt::U(0), &ty::Uint(_)) => Some(Extrema::Minimum),
87+
_ => None,
88+
}
89+
}

tests/ui/cast.rs

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#![allow(
1313
clippy::cast_abs_to_unsigned,
1414
clippy::no_effect,
15+
clippy::unnecessary_min_max,
1516
clippy::unnecessary_operation,
1617
clippy::unnecessary_literal_unwrap
1718
)]

0 commit comments

Comments
 (0)