blob: 413881d5ec99ec7e59e68457be47012e8e62a8ca [file] [log] [blame]
use std::cmp::Ordering;
use super::UNNECESSARY_MIN_OR_MAX;
use clippy_utils::consts::{ConstEvalCtxt, Constant, ConstantSource, FullInt};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::sym;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::{Span, Symbol};
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
name: Symbol,
recv: &'tcx Expr<'_>,
arg: &'tcx Expr<'_>,
) {
let typeck_results = cx.typeck_results();
let ecx = ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), typeck_results);
if let Some(id) = typeck_results.type_dependent_def_id(expr.hir_id)
&& (cx.tcx.is_diagnostic_item(sym::cmp_ord_min, id) || cx.tcx.is_diagnostic_item(sym::cmp_ord_max, id))
{
if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(recv)
&& let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(arg)
{
let Some(ord) = Constant::partial_cmp(cx.tcx, typeck_results.expr_ty(recv), &left, &right) else {
return;
};
lint(cx, expr, name, recv.span, arg.span, ord);
} else if let Some(extrema) = detect_extrema(cx, recv) {
let ord = match extrema {
Extrema::Minimum => Ordering::Less,
Extrema::Maximum => Ordering::Greater,
};
lint(cx, expr, name, recv.span, arg.span, ord);
} else if let Some(extrema) = detect_extrema(cx, arg) {
let ord = match extrema {
Extrema::Minimum => Ordering::Greater,
Extrema::Maximum => Ordering::Less,
};
lint(cx, expr, name, recv.span, arg.span, ord);
}
}
}
fn lint(cx: &LateContext<'_>, expr: &Expr<'_>, name: Symbol, lhs: Span, rhs: Span, order: Ordering) {
let cmp_str = if order.is_ge() { "smaller" } else { "greater" };
let suggested_value = if (name == sym::min && order.is_ge()) || (name == sym::max && order.is_le()) {
snippet(cx, rhs, "..")
} else {
snippet(cx, lhs, "..")
};
span_lint_and_sugg(
cx,
UNNECESSARY_MIN_OR_MAX,
expr.span,
format!(
"`{}` is never {} than `{}` and has therefore no effect",
snippet(cx, lhs, ".."),
cmp_str,
snippet(cx, rhs, "..")
),
"try",
suggested_value.to_string(),
Applicability::MachineApplicable,
);
}
#[derive(Debug)]
enum Extrema {
Minimum,
Maximum,
}
fn detect_extrema<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Extrema> {
let ty = cx.typeck_results().expr_ty(expr);
let cv = ConstEvalCtxt::new(cx).eval(expr)?;
match (cv.int_value(cx.tcx, ty)?, ty.kind()) {
(FullInt::S(i), &ty::Int(ity)) if i == i128::MIN >> (128 - ity.bit_width()?) => Some(Extrema::Minimum),
(FullInt::S(i), &ty::Int(ity)) if i == i128::MAX >> (128 - ity.bit_width()?) => Some(Extrema::Maximum),
(FullInt::U(i), &ty::Uint(uty)) if i == u128::MAX >> (128 - uty.bit_width()?) => Some(Extrema::Maximum),
(FullInt::U(0), &ty::Uint(_)) => Some(Extrema::Minimum),
_ => None,
}
}