| 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::{Spanned, SyntaxContext}; | |
| 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); | |
| } | |
| } |