blob: e0a6498f62fd477fd241ebfcdef5ad8dae7cfadf [file] [log] [blame] [edit]
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg::Sugg;
use clippy_utils::{get_parent_expr, has_ambiguous_literal_in_expr, sym};
use rustc_ast::AssignOpKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment};
use rustc_lint::LateContext;
use rustc_span::source_map::Spanned;
use super::SUBOPTIMAL_FLOPS;
fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
lhs,
rhs,
) = expr.kind
&& cx.typeck_results().expr_ty(lhs).is_floating_point()
&& cx.typeck_results().expr_ty(rhs).is_floating_point()
{
return Some((lhs, rhs));
}
None
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
let (is_assign, op, lhs, rhs) = match &expr.kind {
ExprKind::AssignOp(
Spanned {
node: AssignOpKind::AddAssign,
..
},
lhs,
rhs,
) => (true, BinOpKind::Add, lhs, rhs),
ExprKind::AssignOp(
Spanned {
node: AssignOpKind::SubAssign,
..
},
lhs,
rhs,
) => (true, BinOpKind::Sub, lhs, rhs),
ExprKind::Binary(
Spanned {
node: op @ (BinOpKind::Add | BinOpKind::Sub),
..
},
lhs,
rhs,
) => (false, *op, lhs, rhs),
_ => return,
};
if !is_assign
&& let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = parent.kind
&& method.name == sym::sqrt
// we don't care about the applicability as this is an early-return condition
&& super::hypot::detect(cx, receiver, &mut Applicability::Unspecified).is_some()
{
return;
}
// Check if any variable in the expression has an ambiguous type (could be f32 or f64)
// see: https://github.com/rust-lang/rust-clippy/issues/14897
let has_ambiguous_type = |expr: &Expr<'_>| {
(matches!(expr.kind, ExprKind::Path(_)) || matches!(expr.kind, ExprKind::Call(_, _)))
&& has_ambiguous_literal_in_expr(cx, expr)
};
let (recv, arg1, arg2, is_from_rhs) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs)
&& cx.typeck_results().expr_ty(lhs).is_floating_point()
&& !has_ambiguous_type(inner_lhs)
{
(inner_lhs, inner_rhs, lhs, true)
} else if !is_assign
&& let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs)
&& cx.typeck_results().expr_ty(rhs).is_floating_point()
&& !has_ambiguous_type(inner_lhs)
{
(inner_lhs, inner_rhs, rhs, false)
} else {
return;
};
span_lint_and_then(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"multiply and add expressions can be calculated more efficiently and accurately",
|diag| {
let maybe_neg_sugg = |expr, app: &mut _| {
let sugg = Sugg::hir_with_applicability(cx, expr, "_", app);
if let BinOpKind::Sub = op { -sugg } else { sugg }
};
let mut app = Applicability::MachineApplicable;
let recv_sugg = super::lib::prepare_receiver_sugg(cx, recv, &mut app);
let (arg1, arg2) = if is_from_rhs {
(
maybe_neg_sugg(arg1, &mut app),
Sugg::hir_with_applicability(cx, arg2, "_", &mut app),
)
} else {
(
Sugg::hir_with_applicability(cx, arg1, "_", &mut app),
maybe_neg_sugg(arg2, &mut app),
)
};
diag.span_suggestion(
expr.span,
"consider using",
if is_assign {
format!("{arg2} = {recv_sugg}.mul_add({arg1}, {arg2})")
} else {
format!("{recv_sugg}.mul_add({arg1}, {arg2})")
},
app,
);
},
);
}