blob: d12a32e1588145325f1df7ce801c7affa1ba46b2 [file] [log] [blame] [edit]
use clippy_utils::consts::ConstEvalCtxt;
use clippy_utils::consts::Constant::{F32, F64, Int};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use clippy_utils::{eq_expr_value, higher, peel_blocks};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_lint::LateContext;
use rustc_span::SyntaxContext;
use rustc_span::source_map::Spanned;
use super::SUBOPTIMAL_FLOPS;
/// Returns true iff expr is an expression which tests whether or not
/// test is positive or an expression which tests whether or not test
/// is nonnegative.
/// Used for check-custom-abs function below
fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
match op {
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test),
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test),
_ => false,
}
} else {
false
}
}
/// See [`is_testing_positive`]
fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
match op {
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test),
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test),
_ => false,
}
} else {
false
}
}
/// Returns true iff expr is some zero literal
fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool {
match ConstEvalCtxt::new(cx).eval_local(expr, ctxt) {
Some(Int(i)) => i == 0,
Some(F32(f)) => f == 0.0,
Some(F64(f)) => f == 0.0,
_ => false,
}
}
/// If the two expressions are negations of each other, then it returns
/// a tuple, in which the first element is true iff expr1 is the
/// positive expressions, and the second element is the positive
/// one of the two expressions
/// If the two expressions are not negations of each other, then it
/// returns None.
fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> {
if let ExprKind::Unary(UnOp::Neg, expr1_negated) = expr1.kind
&& eq_expr_value(cx, expr1_negated, expr2)
{
return Some((false, expr2));
}
if let ExprKind::Unary(UnOp::Neg, expr2_negated) = expr2.kind
&& eq_expr_value(cx, expr1, expr2_negated)
{
return Some((true, expr1));
}
None
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let Some(higher::If {
cond,
then,
r#else: Some(r#else),
}) = higher::If::hir(expr)
&& let if_body_expr = peel_blocks(then)
&& let else_body_expr = peel_blocks(r#else)
&& let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr)
{
let sugg_positive_abs = if is_testing_positive(cx, cond, body) {
if_expr_positive
} else if is_testing_negative(cx, cond, body) {
!if_expr_positive
} else {
return;
};
let mut app = Applicability::MachineApplicable;
let body = Sugg::hir_with_applicability(cx, body, "_", &mut app).maybe_paren();
let sugg = if sugg_positive_abs {
("manual implementation of `abs` method", format!("{body}.abs()"))
} else {
#[rustfmt::skip]
("manual implementation of negation of `abs` method", format!("-{body}.abs()"))
};
span_lint_and_sugg(cx, SUBOPTIMAL_FLOPS, expr.span, sugg.0, "try", sugg.1, app);
}
}